diff --git a/tests/SharpEmu.Libs.Tests/LibcInternal/LibcInternalExportsTests.cs b/tests/SharpEmu.Libs.Tests/LibcInternal/LibcInternalExportsTests.cs new file mode 100644 index 00000000..d0d89b1b --- /dev/null +++ b/tests/SharpEmu.Libs.Tests/LibcInternal/LibcInternalExportsTests.cs @@ -0,0 +1,137 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System.Buffers.Binary; + +using SharpEmu.HLE; +using SharpEmu.Libs.LibcInternal; + +using Xunit; + +namespace SharpEmu.Libs.Tests.LibcInternal; + +public sealed class LibcInternalExportsTests +{ + private const ulong Base = 0x3_0000_0000; + private const ulong InfoAddress = Base + 0x100; + private const ulong ExpectedInfoSize = 32; + + [Fact] + public void HeapGetTraceInfo_NullPointer_ReturnsInvalidArgument() + { + var memory = new FakeCpuMemory(Base, 0x1000); + var context = new CpuContext(memory, Generation.Gen5); + + context[CpuRegister.Rdi] = 0; + + var result = LibcInternalExports.LibcHeapGetTraceInfo(context); + + Assert.Equal( + (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT, + result); + Assert.Equal(0UL, context[CpuRegister.Rax]); + } + + [Fact] + public void HeapGetTraceInfo_WrongSize_ReturnsInvalidArgument() + { + var memory = new FakeCpuMemory(Base, 0x1000); + var context = new CpuContext(memory, Generation.Gen5); + + Span sizeBytes = stackalloc byte[sizeof(ulong)]; + BinaryPrimitives.WriteUInt64LittleEndian( + sizeBytes, + ExpectedInfoSize - 1); + + Assert.True(memory.TryWrite(InfoAddress, sizeBytes)); + + context[CpuRegister.Rdi] = InfoAddress; + + var result = LibcInternalExports.LibcHeapGetTraceInfo(context); + + Assert.Equal( + (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT, + result); + Assert.Equal(0UL, context[CpuRegister.Rax]); + } + + [Fact] + public void HeapGetTraceInfo_ValidBuffer_WritesStablePointers() + { + var memory = new FakeCpuMemory(Base, 0x1000); + var context = new CpuContext(memory, Generation.Gen5); + + Span sizeBytes = stackalloc byte[sizeof(ulong)]; + BinaryPrimitives.WriteUInt64LittleEndian( + sizeBytes, + ExpectedInfoSize); + + Assert.True(memory.TryWrite(InfoAddress, sizeBytes)); + + context[CpuRegister.Rdi] = InfoAddress; + + var firstResult = + LibcInternalExports.LibcHeapGetTraceInfo(context); + + Assert.Equal( + (int)OrbisGen2Result.ORBIS_GEN2_OK, + firstResult); + Assert.Equal(0UL, context[CpuRegister.Rax]); + + Assert.True( + context.TryReadUInt64( + InfoAddress + 16, + out var firstMaskAddress)); + + Assert.True( + context.TryReadUInt64( + InfoAddress + 24, + out var firstTableAddress)); + + Assert.NotEqual(0UL, firstMaskAddress); + Assert.Equal(firstMaskAddress + 8UL, firstTableAddress); + + var secondResult = + LibcInternalExports.LibcHeapGetTraceInfo(context); + + Assert.Equal( + (int)OrbisGen2Result.ORBIS_GEN2_OK, + secondResult); + + Assert.True( + context.TryReadUInt64( + InfoAddress + 16, + out var secondMaskAddress)); + + Assert.True( + context.TryReadUInt64( + InfoAddress + 24, + out var secondTableAddress)); + + Assert.Equal(firstMaskAddress, secondMaskAddress); + Assert.Equal(firstTableAddress, secondTableAddress); + } + + [Fact] + public void HeapGetTraceInfo_TruncatedOutput_ReturnsMemoryFault() + { + var memory = new FakeCpuMemory(Base, 31); + var context = new CpuContext(memory, Generation.Gen5); + + Span sizeBytes = stackalloc byte[sizeof(ulong)]; + BinaryPrimitives.WriteUInt64LittleEndian( + sizeBytes, + ExpectedInfoSize); + + Assert.True(memory.TryWrite(Base, sizeBytes)); + + context[CpuRegister.Rdi] = Base; + + var result = LibcInternalExports.LibcHeapGetTraceInfo(context); + + Assert.Equal( + (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT, + result); + Assert.Equal(0UL, context[CpuRegister.Rax]); + } +} \ No newline at end of file