// Copyright (C) 2026 SharpEmu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later using System.Buffers.Binary; using SharpEmu.Core.Loader; using SharpEmu.Core.Memory; using SharpEmu.HLE; using SharpEmu.Libs.Tests.Kernel; using Xunit; namespace SharpEmu.Libs.Tests.Loader; [Collection(KernelMemoryCompatStateCollection.Name)] public sealed class TlsInitializationTests { [Fact] public void LoadSeedsEachThreadWithRelocatedInitializationBytes() { try { var memory = new VirtualMemory(); var image = new SelfLoader().Load(CreateImage(), memory); var imageBase = image.EntryPoint - image.ElfHeader.EntryPoint; var expectedPointer = imageBase + 0x600; var mappedPointer = new byte[8]; Assert.True(memory.TryRead(imageBase + 0x500, mappedPointer)); Assert.Equal(expectedPointer, BinaryPrimitives.ReadUInt64LittleEndian(mappedPointer)); Assert.Equal(expectedPointer, BinaryPrimitives.ReadUInt64LittleEndian(GuestTlsTemplate.InitImage)); var context = new CpuContext(new FakeCpuMemory(0x10000, 0x40000), Generation.Gen5); foreach (var threadPointer in new ulong[] { 0x20000, 0x30000 }) { GuestTlsTemplate.SeedThreadBlock(context, threadPointer); var threadBytes = new byte[0x20]; Assert.True(context.Memory.TryRead(threadPointer - image.TlsStaticOffset, threadBytes)); Assert.Equal(expectedPointer, BinaryPrimitives.ReadUInt64LittleEndian(threadBytes)); Assert.Equal(1UL, BinaryPrimitives.ReadUInt64LittleEndian(threadBytes.AsSpan(8))); Assert.All(threadBytes[16..], value => Assert.Equal(0, value)); Assert.True(context.Memory.TryWrite(threadPointer - image.TlsStaticOffset, new byte[8])); } } finally { GuestTlsTemplate.Reset(); } } [Fact] public void InitializationUpdatePreservesLayoutAndRejectsLiveThreadChanges() { try { GuestTlsTemplate.Reset(); var staticOffset = GuestTlsTemplate.RegisterModule(1, new byte[8], 0x20, 0x10); var generation = GuestTlsTemplate.Generation; byte[] initializationImage = [1, 2, 3, 4, 5, 6, 7, 8]; GuestTlsTemplate.UpdateInitializationImage(1, initializationImage); Assert.Equal(generation, GuestTlsTemplate.Generation); Assert.Equal(staticOffset, GuestTlsTemplate.BlockSize); initializationImage[0] = 0; Assert.Equal(1, GuestTlsTemplate.InitImage[0]); Assert.Throws(() => GuestTlsTemplate.UpdateInitializationImage(1, new byte[9])); Assert.Throws(() => GuestTlsTemplate.UpdateInitializationImage(2, new byte[8])); var context = new CpuContext(new FakeCpuMemory(0x10000, 0x20000), Generation.Gen5); GuestTlsTemplate.SeedThreadBlock(context, 0x20000); Assert.Throws(() => GuestTlsTemplate.UpdateInitializationImage(1, new byte[8])); GuestTlsTemplate.RegisterModule(2, new byte[8], 0x20, 0x10); GuestTlsTemplate.UpdateInitializationImage(2, initializationImage); } finally { GuestTlsTemplate.Reset(); } } [Theory] [InlineData(0x21UL, 0x20UL, 0x500UL)] [InlineData(8UL, 0x20UL, 0x2000UL)] public void LoadRejectsInvalidInitializationRange(ulong fileSize, ulong memorySize, ulong virtualAddress) { try { var image = CreateImage(); BinaryPrimitives.WriteUInt64LittleEndian(image.AsSpan(0xB0 + 0x10), virtualAddress); BinaryPrimitives.WriteUInt64LittleEndian(image.AsSpan(0xB0 + 0x20), fileSize); BinaryPrimitives.WriteUInt64LittleEndian(image.AsSpan(0xB0 + 0x28), memorySize); Assert.Throws(() => new SelfLoader().Load(image, new VirtualMemory())); } finally { GuestTlsTemplate.Reset(); } } private static byte[] CreateImage() { var image = new byte[0x1000]; new byte[] { 0x7F, (byte)'E', (byte)'L', (byte)'F', 2, 1, 1, 9, 2 }.CopyTo(image, 0); BinaryPrimitives.WriteUInt16LittleEndian(image.AsSpan(0x10), 3); BinaryPrimitives.WriteUInt16LittleEndian(image.AsSpan(0x12), 62); BinaryPrimitives.WriteUInt32LittleEndian(image.AsSpan(0x14), 1); BinaryPrimitives.WriteUInt64LittleEndian(image.AsSpan(0x20), 0x40); BinaryPrimitives.WriteUInt16LittleEndian(image.AsSpan(0x34), 0x40); BinaryPrimitives.WriteUInt16LittleEndian(image.AsSpan(0x36), 0x38); BinaryPrimitives.WriteUInt16LittleEndian(image.AsSpan(0x38), 3); WriteProgramHeader(image.AsSpan(0x40), 1, 0, 0x1000, 0x1000); WriteProgramHeader(image.AsSpan(0x78), 2, 0x200, 0x40, 0x40); WriteProgramHeader(image.AsSpan(0xB0), 7, 0x500, 0x10, 0x20); WriteDynamicEntry(image.AsSpan(0x200), 7, 0x300); WriteDynamicEntry(image.AsSpan(0x210), 8, 0x30); WriteDynamicEntry(image.AsSpan(0x220), 9, 0x18); WriteRelocation(image.AsSpan(0x300), 0x500, 8, 0x600); WriteRelocation(image.AsSpan(0x318), 0x508, 16, 0); return image; } private static void WriteProgramHeader(Span header, uint type, ulong offset, ulong fileSize, ulong memorySize) { BinaryPrimitives.WriteUInt32LittleEndian(header, type); BinaryPrimitives.WriteUInt32LittleEndian(header[4..], 6); BinaryPrimitives.WriteUInt64LittleEndian(header[8..], offset); BinaryPrimitives.WriteUInt64LittleEndian(header[0x10..], offset); BinaryPrimitives.WriteUInt64LittleEndian(header[0x20..], fileSize); BinaryPrimitives.WriteUInt64LittleEndian(header[0x28..], memorySize); BinaryPrimitives.WriteUInt64LittleEndian(header[0x30..], 0x10); } private static void WriteDynamicEntry(Span entry, ulong tag, ulong value) { BinaryPrimitives.WriteUInt64LittleEndian(entry, tag); BinaryPrimitives.WriteUInt64LittleEndian(entry[8..], value); } private static void WriteRelocation(Span entry, ulong targetOffset, ulong type, ulong addend) { BinaryPrimitives.WriteUInt64LittleEndian(entry, targetOffset); BinaryPrimitives.WriteUInt64LittleEndian(entry[8..], type); BinaryPrimitives.WriteUInt64LittleEndian(entry[16..], addend); } }