diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RISCV64/RISCV64Unwinder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RISCV64/RISCV64Unwinder.cs index 55a1d8acab2c7c..30804e1e0355e4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RISCV64/RISCV64Unwinder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RISCV64/RISCV64Unwinder.cs @@ -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 @@ -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, @@ -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); 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 } @@ -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; // @@ -227,7 +228,7 @@ private bool VirtualUnwindFull( if (offsetInFunction >= scopeStart) { unwindCodePtr += unwindIndex; - skipWords = offsetInFunction - scopeStart; + skipHalfwords = offsetInFunction - scopeStart; } } } @@ -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; } } @@ -273,7 +274,7 @@ private bool VirtualUnwindFull( // to skip. // - while (unwindCodePtr < unwindCodesEndPtr && skipWords > 0) + while (unwindCodePtr < unwindCodesEndPtr && skipHalfwords > 0) { byte curCode = _target.Read(unwindCodePtr); if (OpcodeIsEnd(curCode)) @@ -281,7 +282,7 @@ private bool VirtualUnwindFull( break; } unwindCodePtr += UnwindCodeSizeTable[curCode]; - skipWords--; + skipHalfwords -= 2; } // @@ -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. @@ -338,6 +339,9 @@ private uint ComputeScopeSize(TargetPointer unwindCodePtr, TargetPointer unwindC scopeSize++; } + if (isEpilog) + scopeSize++; + return scopeSize; } diff --git a/src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs b/src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs index 4095cb4ae95e29..573df68d1ae3ba 100644 --- a/src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs @@ -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 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 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.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() {