mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-25 20:28:48 +08:00
Merge branch 'main' of https://github.com/sharpemu/sharpemu
This commit is contained in:
@@ -362,7 +362,7 @@ public static class KernelExports
|
|||||||
ExportName = "open",
|
ExportName = "open",
|
||||||
Target = Generation.Gen4 | Generation.Gen5,
|
Target = Generation.Gen4 | Generation.Gen5,
|
||||||
LibraryName = "libKernel")]
|
LibraryName = "libKernel")]
|
||||||
public static int Open(CpuContext ctx) => KernelMemoryCompatExports.KernelOpenUnderscore(ctx);
|
public static int Open(CpuContext ctx) => KernelMemoryCompatExports.PosixOpen(ctx);
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "1G3lF1Gg1k8",
|
Nid = "1G3lF1Gg1k8",
|
||||||
@@ -376,7 +376,7 @@ public static class KernelExports
|
|||||||
ExportName = "fstat",
|
ExportName = "fstat",
|
||||||
Target = Generation.Gen4 | Generation.Gen5,
|
Target = Generation.Gen4 | Generation.Gen5,
|
||||||
LibraryName = "libc")]
|
LibraryName = "libc")]
|
||||||
public static int Fstat(CpuContext ctx) => KernelMemoryCompatExports.KernelFstat(ctx);
|
public static int Fstat(CpuContext ctx) => KernelMemoryCompatExports.PosixFstat(ctx);
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "hcuQgD53UxM",
|
Nid = "hcuQgD53UxM",
|
||||||
|
|||||||
@@ -65,7 +65,10 @@ public static partial class KernelMemoryCompatExports
|
|||||||
private const uint HostPageExecuteReadWrite = 0x40;
|
private const uint HostPageExecuteReadWrite = 0x40;
|
||||||
private const uint HostPageExecuteWriteCopy = 0x80;
|
private const uint HostPageExecuteWriteCopy = 0x80;
|
||||||
private const uint HostPageGuard = 0x100;
|
private const uint HostPageGuard = 0x100;
|
||||||
|
private const int Enoent = 2;
|
||||||
|
private const int Ebadf = 9;
|
||||||
private const int Enomem = 12;
|
private const int Enomem = 12;
|
||||||
|
private const int Eacces = 13;
|
||||||
private const int Efault = 14;
|
private const int Efault = 14;
|
||||||
private const int Einval = 22;
|
private const int Einval = 22;
|
||||||
private const int Erange = 34;
|
private const int Erange = 34;
|
||||||
@@ -1545,7 +1548,13 @@ public static partial class KernelMemoryCompatExports
|
|||||||
ExportName = "close",
|
ExportName = "close",
|
||||||
Target = Generation.Gen4 | Generation.Gen5,
|
Target = Generation.Gen4 | Generation.Gen5,
|
||||||
LibraryName = "libKernel")]
|
LibraryName = "libKernel")]
|
||||||
public static int PosixClose(CpuContext ctx) => KernelCloseCore(ctx, unchecked((int)ctx[CpuRegister.Rdi]));
|
public static int PosixClose(CpuContext ctx)
|
||||||
|
{
|
||||||
|
var result = KernelCloseCore(ctx, unchecked((int)ctx[CpuRegister.Rdi]));
|
||||||
|
return result == (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||||
|
? 0
|
||||||
|
: PosixFailure(ctx, result, notFoundErrno: Ebadf);
|
||||||
|
}
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "UK2Tl2DWUns",
|
Nid = "UK2Tl2DWUns",
|
||||||
@@ -1602,6 +1611,29 @@ public static partial class KernelMemoryCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Translates a failed raw Orbis kernel result into the libc/POSIX ABI:
|
||||||
|
// return -1 with errno set. The raw sceKernel* implementations report the
|
||||||
|
// 0x8002xxxx sentinel through the return value, but the POSIX-named exports
|
||||||
|
// (open/fstat/close/read/write/stat) are called by libc code that expects a
|
||||||
|
// negative result on failure. Leaking the raw sentinel makes callers store
|
||||||
|
// it as a "valid" fd or handle and later dereference it - the null-pointer
|
||||||
|
// access violation seen when Unity's IL2CPP file layer probes an absent
|
||||||
|
// il2cpp.usym. fd-based calls pass notFoundErrno=Ebadf; path-based calls
|
||||||
|
// leave the Enoent default.
|
||||||
|
private static int PosixFailure(CpuContext ctx, int orbisResult, int notFoundErrno = Enoent)
|
||||||
|
{
|
||||||
|
var errno = orbisResult switch
|
||||||
|
{
|
||||||
|
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT => Einval,
|
||||||
|
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT => Efault,
|
||||||
|
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED => Eacces,
|
||||||
|
_ => notFoundErrno,
|
||||||
|
};
|
||||||
|
KernelRuntimeCompatExports.TrySetErrno(ctx, errno);
|
||||||
|
ctx[CpuRegister.Rax] = ulong.MaxValue;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "E6ao34wPw+U",
|
Nid = "E6ao34wPw+U",
|
||||||
ExportName = "stat",
|
ExportName = "stat",
|
||||||
@@ -1610,23 +1642,29 @@ public static partial class KernelMemoryCompatExports
|
|||||||
public static int PosixStat(CpuContext ctx)
|
public static int PosixStat(CpuContext ctx)
|
||||||
{
|
{
|
||||||
var result = KernelStat(ctx);
|
var result = KernelStat(ctx);
|
||||||
if (result == (int)OrbisGen2Result.ORBIS_GEN2_OK)
|
return result == (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||||
{
|
? 0
|
||||||
return 0;
|
: PosixFailure(ctx, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// stat(2) follows the libc/POSIX ABI: failures return -1 and expose
|
// POSIX open(2): translates a failed raw open into -1/errno. On success
|
||||||
// the reason through errno. Returning the raw Orbis kernel code here
|
// KernelOpenUnderscore already writes the fd into RAX (the import bridge
|
||||||
// makes callers treat a missing file as a non-negative success value.
|
// prefers a written RAX over the return value), so returning 0 is correct.
|
||||||
var errno = result switch
|
public static int PosixOpen(CpuContext ctx)
|
||||||
{
|
{
|
||||||
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT => Einval,
|
var result = KernelOpenUnderscore(ctx);
|
||||||
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT => Efault,
|
return result == (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||||
_ => 2, // ENOENT
|
? 0
|
||||||
};
|
: PosixFailure(ctx, result);
|
||||||
KernelRuntimeCompatExports.TrySetErrno(ctx, errno);
|
}
|
||||||
ctx[CpuRegister.Rax] = ulong.MaxValue;
|
|
||||||
return -1;
|
// POSIX fstat(2): a bad fd maps to EBADF rather than the path-oriented ENOENT.
|
||||||
|
public static int PosixFstat(CpuContext ctx)
|
||||||
|
{
|
||||||
|
var result = KernelFstat(ctx);
|
||||||
|
return result == (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||||
|
? 0
|
||||||
|
: PosixFailure(ctx, result, notFoundErrno: Ebadf);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
@@ -2096,7 +2134,15 @@ public static partial class KernelMemoryCompatExports
|
|||||||
ExportName = "read",
|
ExportName = "read",
|
||||||
Target = Generation.Gen4 | Generation.Gen5,
|
Target = Generation.Gen4 | Generation.Gen5,
|
||||||
LibraryName = "libKernel")]
|
LibraryName = "libKernel")]
|
||||||
public static int PosixRead(CpuContext ctx) => KernelReadUnderscore(ctx);
|
public static int PosixRead(CpuContext ctx)
|
||||||
|
{
|
||||||
|
// On success KernelReadUnderscore writes the byte count into RAX, which
|
||||||
|
// the import bridge prefers over this return value.
|
||||||
|
var result = KernelReadUnderscore(ctx);
|
||||||
|
return result == (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||||
|
? 0
|
||||||
|
: PosixFailure(ctx, result, notFoundErrno: Ebadf);
|
||||||
|
}
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "Cg4srZ6TKbU",
|
Nid = "Cg4srZ6TKbU",
|
||||||
@@ -2295,7 +2341,15 @@ public static partial class KernelMemoryCompatExports
|
|||||||
ExportName = "write",
|
ExportName = "write",
|
||||||
Target = Generation.Gen4 | Generation.Gen5,
|
Target = Generation.Gen4 | Generation.Gen5,
|
||||||
LibraryName = "libKernel")]
|
LibraryName = "libKernel")]
|
||||||
public static int PosixWrite(CpuContext ctx) => KernelWriteUnderscore(ctx);
|
public static int PosixWrite(CpuContext ctx)
|
||||||
|
{
|
||||||
|
// On success KernelWriteUnderscore writes the byte count into RAX, which
|
||||||
|
// the import bridge prefers over this return value.
|
||||||
|
var result = KernelWriteUnderscore(ctx);
|
||||||
|
return result == (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||||
|
? 0
|
||||||
|
: PosixFailure(ctx, result, notFoundErrno: Ebadf);
|
||||||
|
}
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "4wSze92BhLI",
|
Nid = "4wSze92BhLI",
|
||||||
|
|||||||
@@ -5290,10 +5290,20 @@ public static partial class Gen5SpirvTranslator
|
|||||||
UInt(0x108));
|
UInt(0x108));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A wave-mask SGPR (VCC/EXEC) consumed as a per-lane predicate — the
|
||||||
|
// condition of VCndmask, a VCC/EXEC branch, or the derived _vcc/_exec
|
||||||
|
// bool — must be tested at the CURRENT lane's bit, exactly as the
|
||||||
|
// hardware does, not as "the 64-bit value is non-zero". The two coincide
|
||||||
|
// for comparison results (only the lane's own bit is ever set), so the
|
||||||
|
// single-lane path historically used a cheaper whole-word non-zero test.
|
||||||
|
// But bitwise-complement wave-mask idioms (S_NOT/S_ORN2/S_ANDN2/S_NAND/
|
||||||
|
// S_NOR on a 64-bit mask) set the unused upper 63 bits; a whole-word test
|
||||||
|
// then reports "lane active" even when this lane's bit is clear. Unity's
|
||||||
|
// PostProcessing NaN killer does exactly this (`anyNaN | ~allFinite`),
|
||||||
|
// which made every valid pixel read as NaN and get replaced with 0 —
|
||||||
|
// zeroing the whole scene before tonemap. Extract the lane bit always.
|
||||||
private uint IsWaveMaskActive(uint mask) =>
|
private uint IsWaveMaskActive(uint mask) =>
|
||||||
_subgroupInvocationIdInput == 0
|
IsCurrentLaneSet(mask);
|
||||||
? IsNotZero64(mask)
|
|
||||||
: IsCurrentLaneSet(mask);
|
|
||||||
|
|
||||||
private uint IsCurrentLaneSet(uint mask) =>
|
private uint IsCurrentLaneSet(uint mask) =>
|
||||||
IsNotZero64(
|
IsNotZero64(
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using System.Buffers.Binary;
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
using SharpEmu.ShaderCompiler;
|
||||||
|
using SharpEmu.ShaderCompiler.Vulkan;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SharpEmu.Libs.Tests.Agc;
|
||||||
|
|
||||||
|
// Regression tests for how a VCC/EXEC wave mask consumed as a per-lane predicate
|
||||||
|
// is lowered to SPIR-V. A wave mask must be tested at the current lane's bit
|
||||||
|
// (mask & lane_bit) — exactly as the hardware evaluates the VCndmask condition or
|
||||||
|
// a VCC/EXEC branch — not with a whole-word "the 64-bit value is non-zero" test.
|
||||||
|
//
|
||||||
|
// The two agree for comparison results (only the lane's own bit is ever set), but
|
||||||
|
// diverge for the bitwise-complement wave-mask idioms (S_NOT / S_ORN2 / S_ANDN2 /
|
||||||
|
// S_NAND / S_NOR), which set the unused upper 63 bits. A whole-word test then
|
||||||
|
// reports the lane active even when its bit is clear. Unity's PostProcessing NaN
|
||||||
|
// killer combines its channels as `anyNaN | ~allFinite` (S_ORN2_B64); under the
|
||||||
|
// whole-word test every valid pixel read as NaN and was replaced with 0, zeroing
|
||||||
|
// the whole HDR scene before tone-mapping.
|
||||||
|
public sealed class Gen5WaveMaskSpirvTests
|
||||||
|
{
|
||||||
|
private const ulong ShaderAddress = 0x1_0000_0000;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WaveMaskPredicate_IsTestedAtCurrentLaneBit()
|
||||||
|
{
|
||||||
|
// V_CMP_EQ_F32 vcc, v0, v1 writes VCC at run time, which re-materialises
|
||||||
|
// the per-lane _vcc predicate from the wave mask via IsWaveMaskActive.
|
||||||
|
var spirv = Compile([0x7C04_0300u]);
|
||||||
|
|
||||||
|
// The lane's bit in single-lane emulation is the 64-bit constant 1, so the
|
||||||
|
// predicate is `(mask & 1) != 0`. The whole-word bug emitted `mask != 0`
|
||||||
|
// with no such mask. Require the lane-bit AND to be present.
|
||||||
|
Assert.True(
|
||||||
|
ContainsLaneBitMaskedWaveTest(spirv),
|
||||||
|
"wave-mask predicate must be tested at the current lane bit "
|
||||||
|
+ "(mask & lane_bit), not as a whole-word non-zero test");
|
||||||
|
}
|
||||||
|
|
||||||
|
// True when the module contains an OpBitwiseAnd whose operand is a 64-bit
|
||||||
|
// constant of value 1 — the current-lane bit that IsCurrentLaneSet masks the
|
||||||
|
// wave mask with before the non-zero test.
|
||||||
|
private static bool ContainsLaneBitMaskedWaveTest(byte[] spirv)
|
||||||
|
{
|
||||||
|
var laneBitConstIds = new HashSet<uint>();
|
||||||
|
|
||||||
|
// Pass 1: collect 64-bit OpConstant result-ids whose value is 1.
|
||||||
|
foreach (var (op, wordCount, offset) in EnumerateInstructions(spirv))
|
||||||
|
{
|
||||||
|
// OpConstant = 43; a 64-bit constant occupies 5 words
|
||||||
|
// (opcode, resultType, resultId, valueLow, valueHigh).
|
||||||
|
if (op != 43 || wordCount != 5)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultId = ReadWord(spirv, offset + 8);
|
||||||
|
var low = ReadWord(spirv, offset + 12);
|
||||||
|
var high = ReadWord(spirv, offset + 16);
|
||||||
|
if (low == 1 && high == 0)
|
||||||
|
{
|
||||||
|
laneBitConstIds.Add(resultId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass 2: look for an OpBitwiseAnd that consumes one of those constants.
|
||||||
|
foreach (var (op, wordCount, offset) in EnumerateInstructions(spirv))
|
||||||
|
{
|
||||||
|
// OpBitwiseAnd = 199 (opcode, resultType, resultId, operand0, operand1).
|
||||||
|
if (op != 199 || wordCount != 5)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var operand0 = ReadWord(spirv, offset + 12);
|
||||||
|
var operand1 = ReadWord(spirv, offset + 16);
|
||||||
|
if (laneBitConstIds.Contains(operand0) || laneBitConstIds.Contains(operand1))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<(ushort Op, int WordCount, int Offset)> EnumerateInstructions(
|
||||||
|
byte[] spirv)
|
||||||
|
{
|
||||||
|
// 5-word SPIR-V header, then (wordCount << 16 | opcode) packed instructions.
|
||||||
|
for (var offset = 5 * sizeof(uint); offset + sizeof(uint) <= spirv.Length;)
|
||||||
|
{
|
||||||
|
var word = ReadWord(spirv, offset);
|
||||||
|
var wordCount = (int)(word >> 16);
|
||||||
|
if (wordCount <= 0)
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return ((ushort)word, wordCount, offset);
|
||||||
|
offset += wordCount * sizeof(uint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint ReadWord(byte[] spirv, int offset) =>
|
||||||
|
BinaryPrimitives.ReadUInt32LittleEndian(spirv.AsSpan(offset, sizeof(uint)));
|
||||||
|
|
||||||
|
private static byte[] Compile(uint[] programWords)
|
||||||
|
{
|
||||||
|
var memory = new FakeCpuMemory(ShaderAddress, 0x2000);
|
||||||
|
var ctx = new CpuContext(memory, Generation.Gen5);
|
||||||
|
Gen5ShaderAtomicDecodeTests.WriteProgram(memory, ShaderAddress, programWords);
|
||||||
|
var shaderRegisters = new Dictionary<uint, uint>
|
||||||
|
{
|
||||||
|
[Gen5ShaderAtomicDecodeTests.ComputePgmRsrc2Register] = 16u << 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.True(
|
||||||
|
Gen5ShaderTranslator.TryCreateState(
|
||||||
|
ctx,
|
||||||
|
ShaderAddress,
|
||||||
|
0,
|
||||||
|
shaderRegisters,
|
||||||
|
Gen5ShaderAtomicDecodeTests.ComputeUserDataRegister,
|
||||||
|
out var state,
|
||||||
|
out var error),
|
||||||
|
error);
|
||||||
|
Assert.True(
|
||||||
|
Gen5ShaderScalarEvaluator.TryEvaluate(ctx, state, out var evaluation, out error),
|
||||||
|
error);
|
||||||
|
Assert.True(
|
||||||
|
Gen5SpirvTranslator.TryCompileComputeShader(
|
||||||
|
state, evaluation, 1, 1, 1, out var shader, out error),
|
||||||
|
error);
|
||||||
|
return shader.Spirv;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,90 @@ public sealed class KernelMemoryCompatExportsTests
|
|||||||
Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]);
|
Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PosixOpen_MissingFileReturnsMinusOne()
|
||||||
|
{
|
||||||
|
const ulong memoryBase = 0x1_0000_0000;
|
||||||
|
const ulong pathAddress = memoryBase + 0x100;
|
||||||
|
var memory = new FakeCpuMemory(memoryBase, 0x1000);
|
||||||
|
var context = new CpuContext(memory, Generation.Gen5);
|
||||||
|
memory.WriteCString(pathAddress, "/__sharpemu_test_missing__/il2cpp.usym");
|
||||||
|
context[CpuRegister.Rdi] = pathAddress;
|
||||||
|
context[CpuRegister.Rsi] = 0; // O_RDONLY
|
||||||
|
|
||||||
|
var result = KernelMemoryCompatExports.PosixOpen(context);
|
||||||
|
|
||||||
|
// A libc open() failure must be -1, not the raw 0x8002xxxx sentinel the
|
||||||
|
// guest would otherwise store as a valid fd and later dereference.
|
||||||
|
Assert.Equal(-1, result);
|
||||||
|
Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PosixFstat_BadDescriptorReturnsMinusOne()
|
||||||
|
{
|
||||||
|
const ulong memoryBase = 0x1_0000_0000;
|
||||||
|
const ulong statAddress = memoryBase + 0x400;
|
||||||
|
var memory = new FakeCpuMemory(memoryBase, 0x1000);
|
||||||
|
var context = new CpuContext(memory, Generation.Gen5);
|
||||||
|
context[CpuRegister.Rdi] = 0x80020002; // the not-found sentinel misused as an fd
|
||||||
|
context[CpuRegister.Rsi] = statAddress;
|
||||||
|
|
||||||
|
var result = KernelMemoryCompatExports.PosixFstat(context);
|
||||||
|
|
||||||
|
Assert.Equal(-1, result);
|
||||||
|
Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PosixClose_BadDescriptorReturnsMinusOne()
|
||||||
|
{
|
||||||
|
const ulong memoryBase = 0x1_0000_0000;
|
||||||
|
var memory = new FakeCpuMemory(memoryBase, 0x1000);
|
||||||
|
var context = new CpuContext(memory, Generation.Gen5);
|
||||||
|
context[CpuRegister.Rdi] = 0x80020002; // never-opened / sentinel fd
|
||||||
|
|
||||||
|
var result = KernelMemoryCompatExports.PosixClose(context);
|
||||||
|
|
||||||
|
Assert.Equal(-1, result);
|
||||||
|
Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PosixRead_BadDescriptorReturnsMinusOne()
|
||||||
|
{
|
||||||
|
const ulong memoryBase = 0x1_0000_0000;
|
||||||
|
const ulong bufferAddress = memoryBase + 0x200;
|
||||||
|
var memory = new FakeCpuMemory(memoryBase, 0x1000);
|
||||||
|
var context = new CpuContext(memory, Generation.Gen5);
|
||||||
|
context[CpuRegister.Rdi] = 0x80020002; // never-opened / sentinel fd
|
||||||
|
context[CpuRegister.Rsi] = bufferAddress;
|
||||||
|
context[CpuRegister.Rdx] = 0x40;
|
||||||
|
|
||||||
|
var result = KernelMemoryCompatExports.PosixRead(context);
|
||||||
|
|
||||||
|
Assert.Equal(-1, result);
|
||||||
|
Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PosixWrite_BadDescriptorReturnsMinusOne()
|
||||||
|
{
|
||||||
|
const ulong memoryBase = 0x1_0000_0000;
|
||||||
|
const ulong bufferAddress = memoryBase + 0x200;
|
||||||
|
var memory = new FakeCpuMemory(memoryBase, 0x1000);
|
||||||
|
var context = new CpuContext(memory, Generation.Gen5);
|
||||||
|
memory.WriteCString(bufferAddress, "payload");
|
||||||
|
context[CpuRegister.Rdi] = 0x80020002; // never-opened / sentinel fd
|
||||||
|
context[CpuRegister.Rsi] = bufferAddress;
|
||||||
|
context[CpuRegister.Rdx] = 0x7;
|
||||||
|
|
||||||
|
var result = KernelMemoryCompatExports.PosixWrite(context);
|
||||||
|
|
||||||
|
Assert.Equal(-1, result);
|
||||||
|
Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Sprintf_ReadsVariadicDoubleFromXmmRegister()
|
public void Sprintf_ReadsVariadicDoubleFromXmmRegister()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,10 +37,11 @@ public sealed class KernelSocketCompatExportsTests
|
|||||||
KernelMemoryCompatExports.PosixClose(context));
|
KernelMemoryCompatExports.PosixClose(context));
|
||||||
Assert.Equal(0UL, context[CpuRegister.Rax]);
|
Assert.Equal(0UL, context[CpuRegister.Rax]);
|
||||||
|
|
||||||
|
// A second close of an already-closed fd fails per the POSIX ABI:
|
||||||
|
// -1 with errno set, not the raw Orbis NOT_FOUND sentinel.
|
||||||
context[CpuRegister.Rdi] = unchecked((ulong)guestFd);
|
context[CpuRegister.Rdi] = unchecked((ulong)guestFd);
|
||||||
Assert.Equal(
|
Assert.Equal(-1, KernelMemoryCompatExports.PosixClose(context));
|
||||||
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND,
|
Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]);
|
||||||
KernelMemoryCompatExports.PosixClose(context));
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user