Merge pr/fiber-stack-bounds-windows-only
This commit is contained in:
commit
467877d046
|
|
@ -33,6 +33,36 @@ public sealed partial class DirectExecutionBackend
|
|||
EmitUInt32(code, ref offset, field);
|
||||
}
|
||||
|
||||
// Fiber switches replace the guest stack without passing through a guest-entry stub, so the
|
||||
// context-transfer stub keeps the Windows TEB bounds in sync from the target state in R11
|
||||
// (stack top at +160, bottom at +168). Only Windows keeps stack bounds at gs:[8]/gs:[16]: on
|
||||
// macOS gs addresses the pthread TSD, where those slots hold errno's address and another key.
|
||||
// Writing a fiber's bounds there nulled errno, and the next failed libc call on the thread
|
||||
// crashed reading it (Demon's Souls, about 10 s into boot).
|
||||
private static unsafe void EmitFiberStackBounds(byte* code, ref int offset)
|
||||
{
|
||||
if (!OperatingSystem.IsWindows()) return;
|
||||
ReadOnlySpan<byte> loadTop = [0x4D, 0x8B, 0x93, 160, 0, 0, 0]; // mov r10, [r11+160]
|
||||
foreach (var value in loadTop) EmitByte(code, ref offset, value);
|
||||
EmitStackBound(code, ref offset, 10, 8, store: true);
|
||||
ReadOnlySpan<byte> loadBottom = [0x4D, 0x8B, 0x93, 168, 0, 0, 0]; // mov r10, [r11+168]
|
||||
foreach (var value in loadBottom) EmitByte(code, ref offset, value);
|
||||
EmitStackBound(code, ref offset, 10, 16, store: true);
|
||||
}
|
||||
|
||||
// Test hook: the bytes the fiber stack-bounds sync emits on this host.
|
||||
internal static unsafe byte[] EmitFiberStackBoundsForTest()
|
||||
{
|
||||
var buffer = new byte[64];
|
||||
var offset = 0;
|
||||
fixed (byte* code = buffer)
|
||||
{
|
||||
EmitFiberStackBounds(code, ref offset);
|
||||
}
|
||||
|
||||
return buffer[..offset];
|
||||
}
|
||||
|
||||
// R10 points to the entry state. R11 is scratch; RAX remains unchanged.
|
||||
private static unsafe void EmitHostStackBounds(byte* code, ref int offset, bool save)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2279,12 +2279,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||
}
|
||||
|
||||
Emit(0x49); Emit(0x89); Emit(0xC3); // mov r11, rax
|
||||
// Fiber switches replace the guest stack without passing through a
|
||||
// guest-entry stub, so keep the Windows TEB bounds in sync here.
|
||||
EmitLoadFromR11Disp32(10, 160); // r10 = target stack top
|
||||
EmitStackBound(code, ref offset, 10, 8, store: true);
|
||||
EmitLoadFromR11Disp32(10, 168); // r10 = target stack bottom
|
||||
EmitStackBound(code, ref offset, 10, 16, store: true);
|
||||
EmitFiberStackBounds(code, ref offset);
|
||||
// A new >=3.50 fiber receives the SDK-defined MXCSR verbatim. A
|
||||
// resumed fiber follows _sceFiberLongJmp: preserve status bits 0-5
|
||||
// while restoring the saved control bits.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.Core.Cpu.Native;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpEmu.Libs.Tests.Cpu;
|
||||
|
||||
public sealed class FiberStackBoundsTests
|
||||
{
|
||||
[Fact]
|
||||
public void FiberSwitchesTouchGsOnlyWhereItHoldsTheTebStackBounds()
|
||||
{
|
||||
var code = DirectExecutionBackend.EmitFiberStackBoundsForTest();
|
||||
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
// On macOS gs addresses the pthread TSD: gs:[8] is errno's address. A fiber switch
|
||||
// must not store its stack bounds there.
|
||||
Assert.Empty(code);
|
||||
return;
|
||||
}
|
||||
|
||||
// Windows: both TEB bounds (gs:[8] StackBase, gs:[16] StackLimit) are stored from r10.
|
||||
byte[] storeBase = [0x65, 0x4C, 0x89, 0x14, 0x25, 8, 0, 0, 0];
|
||||
byte[] storeLimit = [0x65, 0x4C, 0x89, 0x14, 0x25, 16, 0, 0, 0];
|
||||
Assert.True(code.AsSpan().IndexOf(storeBase) >= 0);
|
||||
Assert.True(code.AsSpan().IndexOf(storeLimit) >= 0);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue