Merge pr/fiber-stack-bounds-windows-only

This commit is contained in:
kuba 2026-09-30 02:45:10 +02:00
commit 467877d046
3 changed files with 61 additions and 6 deletions

View File

@ -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)
{

View File

@ -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.

View File

@ -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);
}
}