mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-22 19:06:15 +08:00
test: add Fiber exports contract tests (13 tests) (#428)
This commit is contained in:
@@ -0,0 +1,332 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Buffers.Binary;
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.Libs.Fiber;
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace SharpEmu.Libs.Tests.Fiber;
|
||||
|
||||
/// <summary>
|
||||
/// Contract tests for the libSceFiber HLE exports. These pin the current
|
||||
/// validation and layout behaviour of <see cref="FiberExports"/>; they do not
|
||||
/// exercise a live guest thread scheduler.
|
||||
/// </summary>
|
||||
public sealed class FiberExportsTests
|
||||
{
|
||||
private const ulong Base = 0x3_0000_0000UL;
|
||||
private const int RegionSize = 0x2000;
|
||||
|
||||
private const int ErrorNull = unchecked((int)0x80590001);
|
||||
private const int ErrorAlignment = unchecked((int)0x80590002);
|
||||
private const int ErrorRange = unchecked((int)0x80590003);
|
||||
private const int ErrorInvalid = unchecked((int)0x80590004);
|
||||
private const int ErrorPermission = unchecked((int)0x80590005);
|
||||
|
||||
private const uint SignatureStart = 0xDEF1649Cu;
|
||||
private const uint SignatureEnd = 0xB37592A0u;
|
||||
private const ulong StackSignature = 0x7149F2CA7149F2CAUL;
|
||||
private const uint StateIdle = 2;
|
||||
|
||||
private const ulong FiberAddress = Base;
|
||||
private const ulong NameAddress = Base + 0x200;
|
||||
private const ulong ContextAddress = Base + 0x400;
|
||||
private const ulong EntryAddress = 0x4_0000_1000UL;
|
||||
private const ulong InfoAddress = Base + 0x800;
|
||||
|
||||
public FiberExportsTests()
|
||||
{
|
||||
FiberExports.ResetRuntimeState();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OptParamInitialize_NullParam_ReturnsNullError()
|
||||
{
|
||||
var memory = new FakeCpuMemory(Base, RegionSize);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
|
||||
context[CpuRegister.Rdi] = 0;
|
||||
|
||||
var result = FiberExports.FiberOptParamInitialize(context);
|
||||
|
||||
Assert.Equal(ErrorNull, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSelf_NullOutAddress_ReturnsNullError()
|
||||
{
|
||||
var memory = new FakeCpuMemory(Base, RegionSize);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
|
||||
context[CpuRegister.Rdi] = 0;
|
||||
|
||||
var result = FiberExports.FiberGetSelf(context);
|
||||
|
||||
Assert.Equal(ErrorNull, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSelf_OutsideFiberContext_ReturnsPermissionError()
|
||||
{
|
||||
var memory = new FakeCpuMemory(Base, RegionSize);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
|
||||
context[CpuRegister.Rdi] = Base + 0x100;
|
||||
|
||||
var result = FiberExports.FiberGetSelf(context);
|
||||
|
||||
Assert.Equal(ErrorPermission, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetInfo_NullInfo_ReturnsNullError()
|
||||
{
|
||||
var memory = new FakeCpuMemory(Base, RegionSize);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
|
||||
context[CpuRegister.Rdi] = FiberAddress;
|
||||
context[CpuRegister.Rsi] = 0;
|
||||
|
||||
var result = FiberExports.FiberGetInfo(context);
|
||||
|
||||
Assert.Equal(ErrorNull, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Initialize_NullFiber_ReturnsNullError()
|
||||
{
|
||||
var memory = new FakeCpuMemory(Base, RegionSize);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
WriteCString(memory, NameAddress, "F");
|
||||
|
||||
context[CpuRegister.Rdi] = 0;
|
||||
context[CpuRegister.Rsi] = NameAddress;
|
||||
context[CpuRegister.Rdx] = EntryAddress;
|
||||
|
||||
var result = FiberExports.FiberInitialize(context);
|
||||
|
||||
Assert.Equal(ErrorNull, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Initialize_NullName_ReturnsNullError()
|
||||
{
|
||||
var memory = new FakeCpuMemory(Base, RegionSize);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
|
||||
context[CpuRegister.Rdi] = FiberAddress;
|
||||
context[CpuRegister.Rsi] = 0;
|
||||
context[CpuRegister.Rdx] = EntryAddress;
|
||||
|
||||
var result = FiberExports.FiberInitialize(context);
|
||||
|
||||
Assert.Equal(ErrorNull, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Initialize_NullEntry_ReturnsNullError()
|
||||
{
|
||||
var memory = new FakeCpuMemory(Base, RegionSize);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
WriteCString(memory, NameAddress, "F");
|
||||
|
||||
context[CpuRegister.Rdi] = FiberAddress;
|
||||
context[CpuRegister.Rsi] = NameAddress;
|
||||
context[CpuRegister.Rdx] = 0;
|
||||
|
||||
var result = FiberExports.FiberInitialize(context);
|
||||
|
||||
Assert.Equal(ErrorNull, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Initialize_MisalignedFiber_ReturnsAlignmentError()
|
||||
{
|
||||
var memory = new FakeCpuMemory(Base, RegionSize);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
WriteCString(memory, NameAddress, "F");
|
||||
|
||||
context[CpuRegister.Rdi] = FiberAddress + 4;
|
||||
context[CpuRegister.Rsi] = NameAddress;
|
||||
context[CpuRegister.Rdx] = EntryAddress;
|
||||
|
||||
var result = FiberExports.FiberInitialize(context);
|
||||
|
||||
Assert.Equal(ErrorAlignment, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Initialize_TooSmallContextSize_ReturnsRangeError()
|
||||
{
|
||||
var memory = new FakeCpuMemory(Base, RegionSize);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
WriteCString(memory, NameAddress, "F");
|
||||
|
||||
context[CpuRegister.Rdi] = FiberAddress;
|
||||
context[CpuRegister.Rsi] = NameAddress;
|
||||
context[CpuRegister.Rdx] = EntryAddress;
|
||||
context[CpuRegister.R8] = ContextAddress;
|
||||
context[CpuRegister.R9] = 256;
|
||||
|
||||
var result = FiberExports.FiberInitialize(context);
|
||||
|
||||
Assert.Equal(ErrorRange, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Initialize_ContextAddressWithoutSize_ReturnsInvalidError()
|
||||
{
|
||||
var memory = new FakeCpuMemory(Base, RegionSize);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
WriteCString(memory, NameAddress, "F");
|
||||
|
||||
context[CpuRegister.Rdi] = FiberAddress;
|
||||
context[CpuRegister.Rsi] = NameAddress;
|
||||
context[CpuRegister.Rdx] = EntryAddress;
|
||||
context[CpuRegister.R8] = ContextAddress;
|
||||
context[CpuRegister.R9] = 0;
|
||||
|
||||
var result = FiberExports.FiberInitialize(context);
|
||||
|
||||
Assert.Equal(ErrorInvalid, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Initialize_Valid_WritesExpectedLayout()
|
||||
{
|
||||
var memory = new FakeCpuMemory(Base, RegionSize);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
const string name = "TestFiber";
|
||||
WriteCString(memory, NameAddress, name);
|
||||
const ulong argOnInitialize = 0xDEADUL;
|
||||
const ulong contextSize = 512UL;
|
||||
|
||||
context[CpuRegister.Rdi] = FiberAddress;
|
||||
context[CpuRegister.Rsi] = NameAddress;
|
||||
context[CpuRegister.Rdx] = EntryAddress;
|
||||
context[CpuRegister.Rcx] = argOnInitialize;
|
||||
context[CpuRegister.R8] = ContextAddress;
|
||||
context[CpuRegister.R9] = contextSize;
|
||||
// RSP defaults to 0 (unmapped); ReadStackArg64 falls back to 0 ->
|
||||
// optParam = 0, buildVersion = 0. ApplyInitializationFlags(0, 0, false) == 0.
|
||||
|
||||
var result = FiberExports.FiberInitialize(context);
|
||||
|
||||
Assert.Equal(0, result);
|
||||
Assert.Equal(0UL, context[CpuRegister.Rax]);
|
||||
|
||||
Assert.Equal(SignatureStart, ReadUInt32(memory, FiberAddress + 0));
|
||||
Assert.Equal(StateIdle, ReadUInt32(memory, FiberAddress + 4));
|
||||
Assert.Equal(EntryAddress, ReadUInt64(memory, FiberAddress + 8));
|
||||
Assert.Equal(argOnInitialize, ReadUInt64(memory, FiberAddress + 16));
|
||||
Assert.Equal(ContextAddress, ReadUInt64(memory, FiberAddress + 24));
|
||||
Assert.Equal(contextSize, ReadUInt64(memory, FiberAddress + 32));
|
||||
AssertInlineName(memory, FiberAddress + 40, name);
|
||||
Assert.Equal(0UL, ReadUInt64(memory, FiberAddress + 72));
|
||||
Assert.Equal(0u, ReadUInt32(memory, FiberAddress + 80));
|
||||
Assert.Equal(ContextAddress, ReadUInt64(memory, FiberAddress + 88));
|
||||
Assert.Equal(ContextAddress + contextSize, ReadUInt64(memory, FiberAddress + 96));
|
||||
Assert.Equal(SignatureEnd, ReadUInt32(memory, FiberAddress + 104));
|
||||
Assert.Equal(StackSignature, ReadUInt64(memory, ContextAddress));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetInfo_AfterInitialize_RoundTripsFields()
|
||||
{
|
||||
var memory = new FakeCpuMemory(Base, RegionSize);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
const string name = "RoundTrip";
|
||||
WriteCString(memory, NameAddress, name);
|
||||
const ulong argOnInitialize = 0xCAFEUL;
|
||||
const ulong contextSize = 512UL;
|
||||
|
||||
context[CpuRegister.Rdi] = FiberAddress;
|
||||
context[CpuRegister.Rsi] = NameAddress;
|
||||
context[CpuRegister.Rdx] = EntryAddress;
|
||||
context[CpuRegister.Rcx] = argOnInitialize;
|
||||
context[CpuRegister.R8] = ContextAddress;
|
||||
context[CpuRegister.R9] = contextSize;
|
||||
|
||||
Assert.Equal(0, FiberExports.FiberInitialize(context));
|
||||
|
||||
WriteUInt64(memory, InfoAddress, 128);
|
||||
context[CpuRegister.Rdi] = FiberAddress;
|
||||
context[CpuRegister.Rsi] = InfoAddress;
|
||||
|
||||
var result = FiberExports.FiberGetInfo(context);
|
||||
|
||||
Assert.Equal(0, result);
|
||||
Assert.Equal(EntryAddress, ReadUInt64(memory, InfoAddress + 8));
|
||||
Assert.Equal(argOnInitialize, ReadUInt64(memory, InfoAddress + 16));
|
||||
Assert.Equal(ContextAddress, ReadUInt64(memory, InfoAddress + 24));
|
||||
Assert.Equal(contextSize, ReadUInt64(memory, InfoAddress + 32));
|
||||
AssertInlineName(memory, InfoAddress + 40, name);
|
||||
Assert.Equal(ulong.MaxValue, ReadUInt64(memory, InfoAddress + 72));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetInfo_WrongSize_ReturnsInvalidError()
|
||||
{
|
||||
var memory = new FakeCpuMemory(Base, RegionSize);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
WriteCString(memory, NameAddress, "F");
|
||||
|
||||
context[CpuRegister.Rdi] = FiberAddress;
|
||||
context[CpuRegister.Rsi] = NameAddress;
|
||||
context[CpuRegister.Rdx] = EntryAddress;
|
||||
context[CpuRegister.R8] = ContextAddress;
|
||||
context[CpuRegister.R9] = 512;
|
||||
|
||||
Assert.Equal(0, FiberExports.FiberInitialize(context));
|
||||
|
||||
WriteUInt64(memory, InfoAddress, 64);
|
||||
context[CpuRegister.Rdi] = FiberAddress;
|
||||
context[CpuRegister.Rsi] = InfoAddress;
|
||||
|
||||
var result = FiberExports.FiberGetInfo(context);
|
||||
|
||||
Assert.Equal(ErrorInvalid, result);
|
||||
}
|
||||
|
||||
private static void WriteCString(FakeCpuMemory memory, ulong address, string text)
|
||||
{
|
||||
memory.WriteCString(address, text);
|
||||
}
|
||||
|
||||
private static void WriteUInt64(FakeCpuMemory memory, ulong address, ulong value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
|
||||
Assert.True(memory.TryWrite(address, buffer));
|
||||
}
|
||||
|
||||
private static uint ReadUInt32(FakeCpuMemory memory, ulong address)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||
Assert.True(memory.TryRead(address, buffer));
|
||||
return BinaryPrimitives.ReadUInt32LittleEndian(buffer);
|
||||
}
|
||||
|
||||
private static ulong ReadUInt64(FakeCpuMemory memory, ulong address)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
|
||||
Assert.True(memory.TryRead(address, buffer));
|
||||
return BinaryPrimitives.ReadUInt64LittleEndian(buffer);
|
||||
}
|
||||
|
||||
private static void AssertInlineName(FakeCpuMemory memory, ulong address, string expected)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[32];
|
||||
Assert.True(memory.TryRead(address, buffer));
|
||||
var length = buffer.IndexOf((byte)0);
|
||||
if (length < 0)
|
||||
{
|
||||
length = buffer.Length;
|
||||
}
|
||||
|
||||
Assert.Equal(expected, System.Text.Encoding.UTF8.GetString(buffer[..length]));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user