From d134f9b9f648ab8709112ab4d26bf5f6f2dc081d Mon Sep 17 00:00:00 2001 From: ParantezTech Date: Tue, 23 Jun 2026 15:48:45 +0300 Subject: [PATCH] [fiber] synchronization problems have been fixed for such a titles: Demon's Souls [ampr] new exports [memory] trampoline fixes --- .../DirectExecutionBackend.Exceptions.cs | 21 +- .../Native/DirectExecutionBackend.Imports.cs | 98 ++---- .../Cpu/Native/DirectExecutionBackend.cs | 318 +++++++++++++++--- .../Memory/PhysicalVirtualMemory.cs | 74 ++++ src/SharpEmu.HLE/GuestThreadExecution.cs | 42 ++- src/SharpEmu.Libs/Ampr/AmprExports.cs | 172 ++++++++++ src/SharpEmu.Libs/Fiber/FiberExports.cs | 143 ++++++-- .../Kernel/KernelAprCompatExports.cs | 26 ++ .../Kernel/KernelEventFlagCompatExports.cs | 144 +++++++- .../Kernel/KernelMemoryCompatExports.cs | 169 ++++++---- .../Kernel/KernelRuntimeCompatExports.cs | 109 ++++-- 11 files changed, 1074 insertions(+), 242 deletions(-) diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Exceptions.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Exceptions.cs index acbe268..643da44 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Exceptions.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Exceptions.cs @@ -19,7 +19,12 @@ public sealed partial class DirectExecutionBackend { if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_DISABLE_RAW_HANDLER"), "1", StringComparison.Ordinal)) { - _rawExceptionHandler = (nint)AddVectoredExceptionHandler(1u, RawVectoredHandlerPtrManaged); + _rawExceptionHandlerStub = CreateExceptionHandlerTrampoline(RawVectoredHandlerPtrManaged); + if (_rawExceptionHandlerStub == 0) + { + throw new InvalidOperationException("Failed to create raw exception handler trampoline"); + } + _rawExceptionHandler = (nint)AddVectoredExceptionHandler(1u, _rawExceptionHandlerStub); Console.Error.WriteLine($"[LOADER][INFO] Raw exception handler installed: 0x{_rawExceptionHandler:X16}"); } else @@ -29,12 +34,22 @@ public sealed partial class DirectExecutionBackend _handlerDelegate = VectoredHandler; _handlerHandle = GCHandle.Alloc(_handlerDelegate); - _exceptionHandler = (nint)AddVectoredExceptionHandler(1u, Marshal.GetFunctionPointerForDelegate(_handlerDelegate)); + _exceptionHandlerStub = CreateExceptionHandlerTrampoline(Marshal.GetFunctionPointerForDelegate(_handlerDelegate)); + if (_exceptionHandlerStub == 0) + { + throw new InvalidOperationException("Failed to create exception handler trampoline"); + } + _exceptionHandler = (nint)AddVectoredExceptionHandler(1u, _exceptionHandlerStub); Console.Error.WriteLine($"[LOADER][INFO] Exception handler installed: 0x{_exceptionHandler:X16}"); _unhandledFilterDelegate = UnhandledExceptionFilter; _unhandledFilterHandle = GCHandle.Alloc(_unhandledFilterDelegate); - SetUnhandledExceptionFilter(Marshal.GetFunctionPointerForDelegate(_unhandledFilterDelegate)); + _unhandledFilterStub = CreateExceptionHandlerTrampoline(Marshal.GetFunctionPointerForDelegate(_unhandledFilterDelegate)); + if (_unhandledFilterStub == 0) + { + throw new InvalidOperationException("Failed to create unhandled exception filter trampoline"); + } + SetUnhandledExceptionFilter(_unhandledFilterStub); } private unsafe int UnhandledExceptionFilter(void* exceptionInfo) diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs index 0063a42..3a63e96 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs @@ -143,6 +143,8 @@ public sealed partial class DirectExecutionBackend ProbeReturnRip(num7, num); } TrackStrlenPrelude(importStubEntry.Nid, num, num7); + TraceGuestContext( + $"import dispatch={num} nid={importStubEntry.Nid} ret=0x{num7:X16} managed={Environment.CurrentManagedThreadId} guest=0x{GuestThreadExecution.CurrentGuestThreadHandle:X16} fiber=0x{GuestThreadExecution.CurrentFiberAddress:X16} active={HasActiveExecutionThread}"); bool logBootstrap = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_BOOTSTRAP"), "1", StringComparison.Ordinal); if (logBootstrap && string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal)) { @@ -279,13 +281,15 @@ public sealed partial class DirectExecutionBackend catch { } - TryBypassStackChkFailTrap(num, num7); } try { OrbisGen2Result orbisGen2Result; bool dispatchResolved = true; - var previousImportCallFrame = GuestThreadExecution.EnterImportCallFrame(num7, (ulong)argPackPtr + 104uL); + var previousImportCallFrame = GuestThreadExecution.EnterImportCallFrame( + num7, + (ulong)argPackPtr + 104uL, + ActiveGuestReturnSlotAddress); try { if (string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal)) @@ -408,7 +412,7 @@ public sealed partial class DirectExecutionBackend private unsafe bool TryForceGuestExitToHostStub(nint argPackPtr, long dispatchIndex, ulong returnRip, string nid) { ulong num = ActiveEntryReturnSentinelRip; - if (num < 65536) + if (num < 65536 || !TryPatchActiveGuestReturnSlot(num)) { return false; } @@ -430,7 +434,7 @@ public sealed partial class DirectExecutionBackend private unsafe bool TryCompleteGuestEntryToHostStub(nint argPackPtr, long dispatchIndex, ulong returnRip, string nid, string reason, int status) { ulong hostExit = ActiveEntryReturnSentinelRip; - if (hostExit < 65536) + if (hostExit < 65536 || !TryPatchActiveGuestReturnSlot(hostExit)) { return false; } @@ -450,7 +454,7 @@ public sealed partial class DirectExecutionBackend private unsafe bool TryYieldGuestThreadToHostStub(nint argPackPtr, long dispatchIndex, ulong returnRip, string nid, string reason) { ulong hostExit = ActiveEntryReturnSentinelRip; - if (hostExit < 65536) + if (hostExit < 65536 || !TryPatchActiveGuestReturnSlot(hostExit)) { return false; } @@ -470,6 +474,14 @@ public sealed partial class DirectExecutionBackend return true; } + private bool TryPatchActiveGuestReturnSlot(ulong hostExit) + { + ulong returnSlotAddress = ActiveGuestReturnSlotAddress; + return returnSlotAddress != 0 && + ActiveCpuContext is not null && + ActiveCpuContext.TryWriteUInt64(returnSlotAddress, hostExit); + } + private bool ShouldForceGuestExitOnImportLoop(string nid, ulong returnRip, long dispatchIndex, ulong arg0, ulong arg1) { if (dispatchIndex < 1200) @@ -480,6 +492,11 @@ public sealed partial class DirectExecutionBackend { return false; } + if (IsImportLoopGuardBoundary(nid)) + { + ResetImportLoopPattern(); + return false; + } if (!_importNidHashCache.TryGetValue(nid, out var value)) { value = StableHash64(nid); @@ -513,6 +530,17 @@ public sealed partial class DirectExecutionBackend return elapsedTicks >= (long)(guardSeconds * Stopwatch.Frequency); } + private static bool IsImportLoopGuardBoundary(string nid) => + string.Equals(nid, "1jfXLRVzisc", StringComparison.Ordinal); + + private void ResetImportLoopPattern() + { + _importLoopPatternHits = 0; + _importLoopPatternStartTimestamp = 0; + _importLoopSignatureCount = 0; + _importLoopSignatureWriteIndex = 0; + } + private static int GetImportLoopGuardSeconds() { if (int.TryParse(Environment.GetEnvironmentVariable("SHARPEMU_IMPORT_LOOP_GUARD_SECONDS"), out var seconds)) @@ -923,66 +951,6 @@ public sealed partial class DirectExecutionBackend } } - private void TryBypassStackChkFailTrap(long dispatchIndex, ulong returnRip) - { - var cpuContext = ActiveCpuContext; - if (cpuContext == null || returnRip < 32) - { - return; - } - try - { - byte[] array = new byte[19]; - ulong num = returnRip - 23; - Marshal.Copy((nint)num, array, 0, array.Length); - if (array[0] != 117 || array[1] != 16 || array[2] != 72 || array[3] != 137 || array[4] != 216 || array[5] != 72 || array[6] != 131 || array[7] != 196 || array[9] != 91 || array[10] != 65 || array[11] != 92 || array[12] != 65 || array[13] != 94 || array[14] != 65 || array[15] != 95 || array[16] != 93 || array[17] != 195 || array[18] != 232) - { - return; - } - ulong value = returnRip - 21; - ulong address = cpuContext[CpuRegister.Rsp]; - if (cpuContext.TryWriteUInt64(address, value)) - { - if (_stackChkBypassSites.Add(num) && TryPatchStackChkFailBranch(num)) - { - Console.Error.WriteLine($"[LOADER][WARNING] Import#{dispatchIndex}: patched stack_chk_fail tail branch at 0x{num:X16} -> NOP NOP"); - } - Console.Error.WriteLine($"[LOADER][WARNING] Import#{dispatchIndex}: redirected __stack_chk_fail return to epilogue 0x{value:X16}"); - } - } - catch - { - } - } - - private unsafe static bool TryPatchStackChkFailBranch(ulong branchAddress) - { - uint flNewProtect = default(uint); - if (!VirtualProtect((void*)branchAddress, 2u, 64u, &flNewProtect)) - { - return false; - } - try - { - if (Marshal.ReadByte((nint)branchAddress) != 117) - { - return false; - } - Marshal.WriteByte((nint)branchAddress, 144); - Marshal.WriteByte((nint)(branchAddress + 1), 144); - FlushInstructionCache(GetCurrentProcess(), (void*)branchAddress, 2u); - return true; - } - catch - { - return false; - } - finally - { - VirtualProtect((void*)branchAddress, 2u, flNewProtect, &flNewProtect); - } - } - private unsafe void TryPatchEa020eLookupCall(long dispatchIndex, ulong returnRip) { if (_patchedEa020eLookupCall || returnRip != 0x0000000800EA01A6uL) diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs index 9cfff64..c6328d5 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs @@ -136,10 +136,18 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I private nint _unresolvedReturnStub; + private nint _guestReturnStub; + private nint _rawExceptionHandler; + private nint _rawExceptionHandlerStub; + private nint _exceptionHandler; + private nint _exceptionHandlerStub; + + private nint _unhandledFilterStub; + private nint _lowIndexedTableScratch; private nint _stackGuardCompareScratch; @@ -161,6 +169,9 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I [ThreadStatic] private static ulong _activeEntryReturnSentinelRip; + [ThreadStatic] + private static ulong _activeGuestReturnSlotAddress; + [ThreadStatic] private static bool _activeForcedGuestExit; @@ -202,8 +213,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I private readonly HashSet _fallbackTrapStubs = new HashSet(); - private readonly HashSet _stackChkBypassSites = new HashSet(); - private readonly HashSet _patchedResolverReturnSites = new HashSet(); private readonly HashSet _patchedTlsImmediateThunkTargets = new HashSet(); @@ -539,6 +548,9 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } } + private ulong ActiveGuestReturnSlotAddress => + HasActiveExecutionThread ? _activeGuestReturnSlotAddress : 0; + private bool ActiveForcedGuestExit { get => HasActiveExecutionThread ? _activeForcedGuestExit : _forcedGuestExit; @@ -591,6 +603,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I DirectExecutionBackend? previousBackend, CpuContext? previousContext, ulong previousSentinel, + ulong previousReturnSlotAddress, bool previousForcedExit, bool previousYieldRequested, string? previousYieldReason) @@ -598,6 +611,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I _activeExecutionBackend = previousBackend; _activeCpuContext = previousContext; _activeEntryReturnSentinelRip = previousSentinel; + _activeGuestReturnSlotAddress = previousReturnSlotAddress; _activeForcedGuestExit = previousForcedExit; _activeGuestThreadYieldRequested = previousYieldRequested; _activeGuestThreadYieldReason = previousYieldReason; @@ -634,6 +648,11 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I throw new OutOfMemoryException("Failed to allocate host stack slot storage"); } _unresolvedReturnStub = CreateUnresolvedReturnStub(); + _guestReturnStub = CreateGuestReturnStub(); + if (_guestReturnStub == 0) + { + throw new OutOfMemoryException("Failed to allocate guest return stub"); + } SetupExceptionHandler(); } @@ -930,9 +949,24 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I yield return "_" + exportName; } - private static bool IsDirectImportTargetUsable(ulong address) + private bool IsDirectImportTargetUsable(ulong address) { - return address >= 65536 && !IsUnresolvedSentinel(address); + if (address < 65536 || IsUnresolvedSentinel(address) || + _cpuContext is null || !TryGetVirtualMemory(_cpuContext, out var virtualMemory)) + { + return false; + } + + foreach (var region in virtualMemory.SnapshotRegions()) + { + if ((region.Protection & ProgramHeaderFlags.Execute) != 0 && + ContainsAddress(region.VirtualAddress, region.MemorySize, address)) + { + return true; + } + } + + return false; } private unsafe void BindTlsBase(CpuContext context) @@ -1243,6 +1277,145 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I return (nint)ptr; } + private unsafe nint CreateGuestReturnStub() + { + const uint stubSize = 256u; + void* ptr = VirtualAlloc(null, stubSize, 12288u, 64u); + if (ptr == null) + { + return 0; + } + + byte* code = (byte*)ptr; + int offset = 0; + EmitByte(code, ref offset, 0x48); // sub rsp, 0x20 + EmitByte(code, ref offset, 0x83); + EmitByte(code, ref offset, 0xEC); + EmitByte(code, ref offset, 0x20); + EmitByte(code, ref offset, 0xB9); // mov ecx, tlsIndex + EmitUInt32(code, ref offset, _hostRspSlotTlsIndex); + EmitByte(code, ref offset, 0x48); // mov rax, TlsGetValue + EmitByte(code, ref offset, 0xB8); + *(long*)(code + offset) = _tlsGetValueAddress; + offset += sizeof(ulong); + EmitByte(code, ref offset, 0xFF); // call rax + EmitByte(code, ref offset, 0xD0); + EmitByte(code, ref offset, 0x48); // add rsp, 0x20 + EmitByte(code, ref offset, 0x83); + EmitByte(code, ref offset, 0xC4); + EmitByte(code, ref offset, 0x20); + EmitByte(code, ref offset, 0x48); // mov rsp, [rax] + EmitByte(code, ref offset, 0x8B); + EmitByte(code, ref offset, 0x20); + EmitHostNonvolatileXmmRestore(code, ref offset); + EmitByte(code, ref offset, 0x41); EmitByte(code, ref offset, 0x5F); + EmitByte(code, ref offset, 0x41); EmitByte(code, ref offset, 0x5E); + EmitByte(code, ref offset, 0x41); EmitByte(code, ref offset, 0x5D); + EmitByte(code, ref offset, 0x41); EmitByte(code, ref offset, 0x5C); + EmitByte(code, ref offset, 0x5E); + EmitByte(code, ref offset, 0x5F); + EmitByte(code, ref offset, 0x5D); + EmitByte(code, ref offset, 0x5B); + EmitByte(code, ref offset, 0xC3); + + uint oldProtect = default; + VirtualProtect(ptr, stubSize, 32u, &oldProtect); + FlushInstructionCache(GetCurrentProcess(), ptr, (nuint)offset); + return (nint)ptr; + } + + private unsafe nint CreateExceptionHandlerTrampoline(nint managedHandler) + { + const uint stubSize = 256u; + void* ptr = VirtualAlloc(null, stubSize, 12288u, 64u); + if (ptr == null) + { + return 0; + } + + byte* code = (byte*)ptr; + int offset = 0; + EmitByte(code, ref offset, 0x41); EmitByte(code, ref offset, 0x54); // push r12 + EmitByte(code, ref offset, 0x41); EmitByte(code, ref offset, 0x55); // push r13 + EmitByte(code, ref offset, 0x49); EmitByte(code, ref offset, 0x89); EmitByte(code, ref offset, 0xE4); // mov r12, rsp + EmitByte(code, ref offset, 0x49); EmitByte(code, ref offset, 0x89); EmitByte(code, ref offset, 0xCD); // mov r13, rcx + EmitByte(code, ref offset, 0x65); EmitByte(code, ref offset, 0x48); // mov rax, gs:[8] + EmitByte(code, ref offset, 0x8B); EmitByte(code, ref offset, 0x04); EmitByte(code, ref offset, 0x25); + EmitUInt32(code, ref offset, 8u); + EmitByte(code, ref offset, 0x49); EmitByte(code, ref offset, 0x39); EmitByte(code, ref offset, 0xC4); // cmp r12, rax + EmitByte(code, ref offset, 0x0F); EmitByte(code, ref offset, 0x83); // jae guestStack + int aboveStackJump = offset; + EmitUInt32(code, ref offset, 0u); + EmitByte(code, ref offset, 0x65); EmitByte(code, ref offset, 0x48); // mov rax, gs:[0x10] + EmitByte(code, ref offset, 0x8B); EmitByte(code, ref offset, 0x04); EmitByte(code, ref offset, 0x25); + EmitUInt32(code, ref offset, 0x10u); + EmitByte(code, ref offset, 0x49); EmitByte(code, ref offset, 0x39); EmitByte(code, ref offset, 0xC4); // cmp r12, rax + EmitByte(code, ref offset, 0x0F); EmitByte(code, ref offset, 0x82); // jb guestStack + int belowStackJump = offset; + EmitUInt32(code, ref offset, 0u); + + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x83); EmitByte(code, ref offset, 0xEC); EmitByte(code, ref offset, 0x28); + EmitByte(code, ref offset, 0x4C); EmitByte(code, ref offset, 0x89); EmitByte(code, ref offset, 0xE9); // mov rcx, r13 + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0xB8); + *(nint*)(code + offset) = managedHandler; + offset += sizeof(nint); + EmitByte(code, ref offset, 0xFF); EmitByte(code, ref offset, 0xD0); + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x83); EmitByte(code, ref offset, 0xC4); EmitByte(code, ref offset, 0x28); + EmitByte(code, ref offset, 0xE9); + int hostRestoreJump = offset; + EmitUInt32(code, ref offset, 0u); + + int guestStackOffset = offset; + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x83); EmitByte(code, ref offset, 0xEC); EmitByte(code, ref offset, 0x28); + EmitByte(code, ref offset, 0xB9); + EmitUInt32(code, ref offset, _hostRspSlotTlsIndex); + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0xB8); + *(nint*)(code + offset) = _tlsGetValueAddress; + offset += sizeof(nint); + EmitByte(code, ref offset, 0xFF); EmitByte(code, ref offset, 0xD0); + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x83); EmitByte(code, ref offset, 0xC4); EmitByte(code, ref offset, 0x28); + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x85); EmitByte(code, ref offset, 0xC0); // test rax, rax + EmitByte(code, ref offset, 0x0F); EmitByte(code, ref offset, 0x84); + int missingTlsJump = offset; + EmitUInt32(code, ref offset, 0u); + EmitByte(code, ref offset, 0x4C); EmitByte(code, ref offset, 0x8B); EmitByte(code, ref offset, 0x18); // mov r11, [rax] + EmitByte(code, ref offset, 0x4D); EmitByte(code, ref offset, 0x85); EmitByte(code, ref offset, 0xDB); // test r11, r11 + EmitByte(code, ref offset, 0x0F); EmitByte(code, ref offset, 0x84); + int missingHostStackJump = offset; + EmitUInt32(code, ref offset, 0u); + EmitByte(code, ref offset, 0x4C); EmitByte(code, ref offset, 0x89); EmitByte(code, ref offset, 0xDC); // mov rsp, r11 + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x83); EmitByte(code, ref offset, 0xEC); EmitByte(code, ref offset, 0x28); + EmitByte(code, ref offset, 0x4C); EmitByte(code, ref offset, 0x89); EmitByte(code, ref offset, 0xE9); // mov rcx, r13 + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0xB8); + *(nint*)(code + offset) = managedHandler; + offset += sizeof(nint); + EmitByte(code, ref offset, 0xFF); EmitByte(code, ref offset, 0xD0); + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x83); EmitByte(code, ref offset, 0xC4); EmitByte(code, ref offset, 0x28); + EmitByte(code, ref offset, 0xE9); + int guestRestoreJump = offset; + EmitUInt32(code, ref offset, 0u); + + int passThroughOffset = offset; + EmitByte(code, ref offset, 0x31); EmitByte(code, ref offset, 0xC0); // xor eax, eax + int restoreOffset = offset; + EmitByte(code, ref offset, 0x4C); EmitByte(code, ref offset, 0x89); EmitByte(code, ref offset, 0xE4); // mov rsp, r12 + EmitByte(code, ref offset, 0x41); EmitByte(code, ref offset, 0x5D); + EmitByte(code, ref offset, 0x41); EmitByte(code, ref offset, 0x5C); + EmitByte(code, ref offset, 0xC3); + + *(int*)(code + aboveStackJump) = guestStackOffset - (aboveStackJump + sizeof(int)); + *(int*)(code + belowStackJump) = guestStackOffset - (belowStackJump + sizeof(int)); + *(int*)(code + hostRestoreJump) = restoreOffset - (hostRestoreJump + sizeof(int)); + *(int*)(code + missingTlsJump) = passThroughOffset - (missingTlsJump + sizeof(int)); + *(int*)(code + missingHostStackJump) = passThroughOffset - (missingHostStackJump + sizeof(int)); + *(int*)(code + guestRestoreJump) = restoreOffset - (guestRestoreJump + sizeof(int)); + + uint oldProtect = default; + VirtualProtect(ptr, stubSize, 32u, &oldProtect); + FlushInstructionCache(GetCurrentProcess(), ptr, (nuint)offset); + return (nint)ptr; + } + private unsafe void* TryAllocateNearEntry(nuint size) { ulong entryPoint = _entryPoint; @@ -1980,8 +2153,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I { Rip = continuation.Rip, Rflags = continuation.Rflags == 0 ? 0x202UL : continuation.Rflags, - FsBase = continuation.FsBase != 0 ? continuation.FsBase : (callerContext.FsBase != 0 ? callerContext.FsBase : fallbackTlsBase), - GsBase = continuation.GsBase != 0 ? continuation.GsBase : (callerContext.GsBase != 0 ? callerContext.GsBase : fallbackTlsBase), + FsBase = callerContext.FsBase != 0 ? callerContext.FsBase : (continuation.FsBase != 0 ? continuation.FsBase : fallbackTlsBase), + GsBase = callerContext.GsBase != 0 ? callerContext.GsBase : (continuation.GsBase != 0 ? continuation.GsBase : fallbackTlsBase), }; context[CpuRegister.Rax] = continuation.Rax; @@ -1997,21 +2170,39 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I context[CpuRegister.R13] = continuation.R13; context[CpuRegister.R14] = continuation.R14; context[CpuRegister.R15] = continuation.R15; - context[CpuRegister.Rsp] = continuation.Rsp + 16uL; + context[CpuRegister.Rsp] = continuation.Rsp; - var currentGuestThreadHandle = GuestThreadExecution.CurrentGuestThreadHandle; var exitReason = GuestNativeCallExitReason.Exception; string? callbackReason = null; string? callbackLastError = null; Exception? callbackException = null; + var currentGuestThreadHandle = GuestThreadExecution.CurrentGuestThreadHandle; + var currentFiberAddress = GuestThreadExecution.CurrentFiberAddress; void RunContinuation() { + var restoreGuestThread = currentGuestThreadHandle != 0 && + GuestThreadExecution.CurrentGuestThreadHandle != currentGuestThreadHandle; + var previousGuestThreadHandle = restoreGuestThread + ? GuestThreadExecution.EnterGuestThread(currentGuestThreadHandle) + : 0UL; + var restoreFiber = currentFiberAddress != 0 && + GuestThreadExecution.CurrentFiberAddress != currentFiberAddress; + var previousFiberAddress = restoreFiber + ? GuestThreadExecution.EnterFiber(currentFiberAddress) + : 0UL; var previousLastError = LastError; try { + TraceGuestContext( + $"continuation-enter reason={reason} managed={Environment.CurrentManagedThreadId} guest=0x{GuestThreadExecution.CurrentGuestThreadHandle:X16} fiber=0x{GuestThreadExecution.CurrentFiberAddress:X16} captured_guest=0x{currentGuestThreadHandle:X16} captured_fiber=0x{currentFiberAddress:X16} restore_guest={restoreGuestThread} restore_fiber={restoreFiber}"); LastError = null; - exitReason = ExecuteGuestContinuationEntry(context, continuation.Rip, reason, out callbackReason); + exitReason = ExecuteGuestContinuationEntry( + context, + continuation.Rip, + continuation.ReturnSlotAddress, + reason, + out callbackReason); callbackLastError = LastError; } catch (Exception ex) @@ -2022,7 +2213,17 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } finally { + TraceGuestContext( + $"continuation-exit reason={reason} managed={Environment.CurrentManagedThreadId} guest=0x{GuestThreadExecution.CurrentGuestThreadHandle:X16} fiber=0x{GuestThreadExecution.CurrentFiberAddress:X16} exit={exitReason}"); LastError = previousLastError; + if (restoreFiber) + { + GuestThreadExecution.RestoreFiber(previousFiberAddress); + } + if (restoreGuestThread) + { + GuestThreadExecution.RestoreGuestThread(previousGuestThreadHandle); + } } } @@ -2045,6 +2246,12 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I { runner.Run(RunContinuation); } + else if (runner is not null) + { + TraceGuestContext( + $"continuation-inline reason={reason} managed={Environment.CurrentManagedThreadId} guest=0x{currentGuestThreadHandle:X16} fiber=0x{currentFiberAddress:X16}"); + RunContinuation(); + } else { RunContinuationOnTemporaryThread(currentGuestThreadHandle, RunContinuation); @@ -2070,6 +2277,14 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I return true; } + private static void TraceGuestContext(string message) + { + if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_CONTEXT"), "1", StringComparison.Ordinal)) + { + Console.Error.WriteLine($"[LOADER][TRACE] guest_context.{message}"); + } + } + private static void RunContinuationOnTemporaryThread(ulong guestThreadHandle, Action continuation) { var continuationThread = new Thread(() => @@ -2351,6 +2566,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I var previousActiveBackend = _activeExecutionBackend; var previousActiveContext = _activeCpuContext; var previousSentinel = _activeEntryReturnSentinelRip; + var previousReturnSlotAddress = _activeGuestReturnSlotAddress; var previousForcedExit = _activeForcedGuestExit; var previousYieldRequested = _activeGuestThreadYieldRequested; var previousYieldReason = _activeGuestThreadYieldReason; @@ -2360,6 +2576,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I _activeExecutionBackend = this; _activeCpuContext = context; _activeEntryReturnSentinelRip = 0; + _activeGuestReturnSlotAddress = 0; _activeForcedGuestExit = false; _activeGuestThreadYieldRequested = false; _activeGuestThreadYieldReason = null; @@ -2463,7 +2680,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I ptr2[offset++] = 91; ptr2[offset++] = 195; ulong sentinel = (ulong)ptr + (ulong)sentinelOffset; - ActiveEntryReturnSentinelRip = sentinel; + ActiveEntryReturnSentinelRip = (ulong)_guestReturnStub; + _activeGuestReturnSlotAddress = context[CpuRegister.Rsp] - 16uL; if (!context.TryWriteUInt64(context[CpuRegister.Rsp], sentinel)) { reason = $"failed to patch guest thread return sentinel at 0x{context[CpuRegister.Rsp]:X16}"; @@ -2509,6 +2727,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I previousActiveBackend, previousActiveContext, previousSentinel, + previousReturnSlotAddress, previousForcedExit, previousYieldRequested, previousYieldReason); @@ -2516,7 +2735,12 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } } - private unsafe GuestNativeCallExitReason ExecuteGuestContinuationEntry(CpuContext context, ulong entryPoint, string name, out string? reason) + private unsafe GuestNativeCallExitReason ExecuteGuestContinuationEntry( + CpuContext context, + ulong entryPoint, + ulong returnSlotAddress, + string name, + out string? reason) { reason = null; if (context[CpuRegister.Rsp] == 0) @@ -2534,6 +2758,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I var previousActiveBackend = _activeExecutionBackend; var previousActiveContext = _activeCpuContext; var previousSentinel = _activeEntryReturnSentinelRip; + var previousReturnSlotAddress = _activeGuestReturnSlotAddress; var previousForcedExit = _activeForcedGuestExit; var previousYieldRequested = _activeGuestThreadYieldRequested; var previousYieldReason = _activeGuestThreadYieldReason; @@ -2543,6 +2768,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I _activeExecutionBackend = this; _activeCpuContext = context; _activeEntryReturnSentinelRip = 0; + _activeGuestReturnSlotAddress = returnSlotAddress; _activeForcedGuestExit = false; _activeGuestThreadYieldRequested = false; _activeGuestThreadYieldReason = null; @@ -2577,7 +2803,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I Emit(0x49); Emit(0x89); Emit(0x22); // mov [r10], rsp EmitMovR64Imm(0x48, 0xB8, context[CpuRegister.Rsp]); // mov rax, guest rsp Emit(0x48); Emit(0x89); Emit(0xC4); // mov rsp, rax - Emit(0x48); Emit(0x83); Emit(0xEC); Emit(0x08); // sub rsp, 8 EmitMovR64Imm(0x48, 0xBB, context[CpuRegister.Rbx]); // mov rbx, imm64 EmitMovR64Imm(0x48, 0xBD, context[CpuRegister.Rbp]); // mov rbp, imm64 EmitMovR64Imm(0x48, 0xBF, context[CpuRegister.Rdi]); // mov rdi, imm64 @@ -2592,29 +2817,11 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I EmitMovR64Imm(0x49, 0xBF, context[CpuRegister.R15]); // mov r15, imm64 EmitMovR64Imm(0x48, 0xB8, context[CpuRegister.Rax]); // mov rax, imm64 EmitMovR64Imm(0x49, 0xBB, entryPoint); // mov r11, entryPoint - Emit(0x41); Emit(0xFF); Emit(0xD3); // call r11 - int sentinelOffset = offset + 4; - Emit(0x48); Emit(0x83); Emit(0xC4); Emit(0x08); // add rsp, 8 - EmitMovR64Imm(0x49, 0xBA, hostRspSlot); // mov r10, hostRspSlot - Emit(0x49); Emit(0x8B); Emit(0x22); // mov rsp, [r10] - EmitHostNonvolatileXmmRestore(ptr2, ref offset); - Emit(0x41); Emit(0x5F); // pop r15 - Emit(0x41); Emit(0x5E); // pop r14 - Emit(0x41); Emit(0x5D); // pop r13 - Emit(0x41); Emit(0x5C); // pop r12 - Emit(0x5E); // pop rsi - Emit(0x5F); // pop rdi - Emit(0x5D); // pop rbp - Emit(0x5B); // pop rbx - Emit(0xC3); // ret - ulong sentinel = (ulong)ptr + (ulong)sentinelOffset; - ActiveEntryReturnSentinelRip = sentinel; - var sentinelStackAddress = context[CpuRegister.Rsp] >= 16uL - ? context[CpuRegister.Rsp] - 16uL - : 0; - if (sentinelStackAddress == 0 || !context.TryWriteUInt64(sentinelStackAddress, sentinel)) + Emit(0x41); Emit(0xFF); Emit(0xE3); // jmp r11 + ActiveEntryReturnSentinelRip = (ulong)_guestReturnStub; + if (returnSlotAddress == 0 || !context.TryWriteUInt64(returnSlotAddress, (ulong)_guestReturnStub)) { - reason = $"failed to patch guest continuation return sentinel at 0x{sentinelStackAddress:X16}"; + reason = $"failed to patch guest continuation return slot at 0x{returnSlotAddress:X16}"; return GuestNativeCallExitReason.Exception; } uint oldProtect = default(uint); @@ -2657,6 +2864,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I previousActiveBackend, previousActiveContext, previousSentinel, + previousReturnSlotAddress, previousForcedExit, previousYieldRequested, previousYieldReason); @@ -2717,9 +2925,18 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } EmitByte(code, ref offset, 0x0F); EmitByte(code, ref offset, store ? (byte)0x7F : (byte)0x6F); - EmitByte(code, ref offset, (byte)(0x44 | ((xmm & 7) << 3))); - EmitByte(code, ref offset, 0x24); - EmitByte(code, ref offset, displacement); + if (displacement < 0x80) + { + EmitByte(code, ref offset, (byte)(0x44 | ((xmm & 7) << 3))); + EmitByte(code, ref offset, 0x24); + EmitByte(code, ref offset, displacement); + } + else + { + EmitByte(code, ref offset, (byte)(0x84 | ((xmm & 7) << 3))); + EmitByte(code, ref offset, 0x24); + EmitUInt32(code, ref offset, displacement); + } } private unsafe bool ExecuteEntry(CpuContext context, ulong entryPoint, out OrbisGen2Result result) @@ -2745,6 +2962,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I var previousActiveBackend = _activeExecutionBackend; var previousActiveContext = _activeCpuContext; var previousSentinel = _activeEntryReturnSentinelRip; + var previousReturnSlotAddress = _activeGuestReturnSlotAddress; var previousForcedExit = _activeForcedGuestExit; var previousYieldRequested = _activeGuestThreadYieldRequested; var previousYieldReason = _activeGuestThreadYieldReason; @@ -2754,6 +2972,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I _activeExecutionBackend = this; _activeCpuContext = context; _activeEntryReturnSentinelRip = 0; + _activeGuestReturnSlotAddress = 0; _activeForcedGuestExit = false; _activeGuestThreadYieldRequested = false; _activeGuestThreadYieldReason = null; @@ -2857,7 +3076,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I ptr2[num3++] = 91; ptr2[num3++] = 195; ulong value = (ulong)ptr + (ulong)num4; - ActiveEntryReturnSentinelRip = value; + ActiveEntryReturnSentinelRip = (ulong)_guestReturnStub; + _activeGuestReturnSlotAddress = context[CpuRegister.Rsp] - 16uL; if (!context.TryWriteUInt64(context[CpuRegister.Rsp], value)) { LastError = $"Failed to patch native return sentinel at 0x{context[CpuRegister.Rsp]:X16}"; @@ -2939,6 +3159,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I previousActiveBackend, previousActiveContext, previousSentinel, + previousReturnSlotAddress, previousForcedExit, previousYieldRequested, previousYieldReason); @@ -3116,6 +3337,22 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I RemoveVectoredExceptionHandler((void*)_rawExceptionHandler); _rawExceptionHandler = 0; } + if (_rawExceptionHandlerStub != 0) + { + VirtualFree((void*)_rawExceptionHandlerStub, 0u, 32768u); + _rawExceptionHandlerStub = 0; + } + if (_exceptionHandlerStub != 0) + { + VirtualFree((void*)_exceptionHandlerStub, 0u, 32768u); + _exceptionHandlerStub = 0; + } + if (_unhandledFilterStub != 0) + { + SetUnhandledExceptionFilter(0); + VirtualFree((void*)_unhandledFilterStub, 0u, 32768u); + _unhandledFilterStub = 0; + } if (_handlerHandle.IsAllocated) { _handlerHandle.Free(); @@ -3172,6 +3409,11 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I VirtualFree((void*)_unresolvedReturnStub, 0u, 32768u); _unresolvedReturnStub = 0; } + if (_guestReturnStub != 0) + { + VirtualFree((void*)_guestReturnStub, 0u, 32768u); + _guestReturnStub = 0; + } if (_lowIndexedTableScratch != 0) { VirtualFree((void*)_lowIndexedTableScratch, 0u, 32768u); diff --git a/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs b/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs index f34006f..14527c0 100644 --- a/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs +++ b/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs @@ -222,6 +222,56 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA return actualAddress; } + public bool TryAllocateAtOrAbove( + ulong desiredAddress, + ulong size, + bool executable, + ulong alignment, + out ulong actualAddress) + { + actualAddress = 0; + if (size == 0) + { + return false; + } + + var alignedSize = AlignUp(size, PageSize); + var effectiveAlignment = Math.Max(PageSize, alignment == 0 ? PageSize : alignment); + var cursor = AlignUp(desiredAddress, effectiveAlignment); + + for (var attempt = 0; attempt < 0x10000; attempt++) + { + if (cursor == 0 || ulong.MaxValue - cursor < alignedSize) + { + return false; + } + + if (TryGetOverlappingRegionEnd(cursor, alignedSize, out var overlapEnd)) + { + cursor = AlignUp(overlapEnd, effectiveAlignment); + continue; + } + + try + { + actualAddress = AllocateAt(cursor, alignedSize, executable, allowAlternative: false); + if (actualAddress == cursor) + { + return true; + } + + actualAddress = 0; + } + catch + { + } + + cursor = AlignUp(cursor + effectiveAlignment, effectiveAlignment); + } + + return false; + } + public bool TryAllocateGuestMemory(ulong size, ulong alignment, out ulong address) { address = 0; @@ -532,6 +582,30 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA return null; } + private bool TryGetOverlappingRegionEnd(ulong address, ulong size, out ulong overlapEnd) + { + overlapEnd = 0; + if (size == 0 || ulong.MaxValue - address < size - 1) + { + return false; + } + + var end = address + size; + lock (_gate) + { + foreach (var region in _regions) + { + var regionEnd = region.VirtualAddress + region.Size; + if (address < regionEnd && region.VirtualAddress < end) + { + overlapEnd = Math.Max(overlapEnd, regionEnd); + } + } + } + + return overlapEnd != 0; + } + private static bool TryResolveRegionOffset(ulong address, ulong size, MemoryRegion region, out ulong offset) { offset = 0; diff --git a/src/SharpEmu.HLE/GuestThreadExecution.cs b/src/SharpEmu.HLE/GuestThreadExecution.cs index e48ef2c..5309fad 100644 --- a/src/SharpEmu.HLE/GuestThreadExecution.cs +++ b/src/SharpEmu.HLE/GuestThreadExecution.cs @@ -36,11 +36,13 @@ public interface IGuestThreadScheduler public readonly record struct GuestImportCallFrame( bool IsValid, ulong ReturnRip, - ulong ResumeRsp); + ulong ResumeRsp, + ulong ReturnSlotAddress); public readonly record struct GuestCpuContinuation( ulong Rip, ulong Rsp, + ulong ReturnSlotAddress, ulong Rflags, ulong FsBase, ulong GsBase, @@ -63,6 +65,9 @@ public static class GuestThreadExecution [ThreadStatic] private static ulong _currentGuestThreadHandle; + [ThreadStatic] + private static ulong _currentFiberAddress; + [ThreadStatic] private static string? _pendingBlockReason; @@ -84,12 +89,17 @@ public static class GuestThreadExecution [ThreadStatic] private static ulong _currentImportResumeRsp; + [ThreadStatic] + private static ulong _currentImportReturnSlotAddress; + public static IGuestThreadScheduler? Scheduler { get; set; } public static bool IsGuestThread => _currentGuestThreadHandle != 0; public static ulong CurrentGuestThreadHandle => _currentGuestThreadHandle; + public static ulong CurrentFiberAddress => _currentFiberAddress; + public static ulong EnterGuestThread(ulong threadHandle) { var previous = _currentGuestThreadHandle; @@ -101,6 +111,7 @@ public static class GuestThreadExecution _hasCurrentImportCallFrame = false; _currentImportReturnRip = 0; _currentImportResumeRsp = 0; + _currentImportReturnSlotAddress = 0; return previous; } @@ -114,6 +125,19 @@ public static class GuestThreadExecution _hasCurrentImportCallFrame = false; _currentImportReturnRip = 0; _currentImportResumeRsp = 0; + _currentImportReturnSlotAddress = 0; + } + + public static ulong EnterFiber(ulong fiberAddress) + { + var previous = _currentFiberAddress; + _currentFiberAddress = fiberAddress; + return previous; + } + + public static void RestoreFiber(ulong previousFiberAddress) + { + _currentFiberAddress = previousFiberAddress; } public static bool RequestCurrentThreadBlock(string reason) @@ -161,15 +185,20 @@ public static class GuestThreadExecution return true; } - public static GuestImportCallFrame EnterImportCallFrame(ulong returnRip, ulong resumeRsp) + public static GuestImportCallFrame EnterImportCallFrame( + ulong returnRip, + ulong resumeRsp, + ulong returnSlotAddress) { var previous = new GuestImportCallFrame( _hasCurrentImportCallFrame, _currentImportReturnRip, - _currentImportResumeRsp); + _currentImportResumeRsp, + _currentImportReturnSlotAddress); _hasCurrentImportCallFrame = true; _currentImportReturnRip = returnRip; _currentImportResumeRsp = resumeRsp; + _currentImportReturnSlotAddress = returnSlotAddress; return previous; } @@ -178,6 +207,7 @@ public static class GuestThreadExecution _hasCurrentImportCallFrame = previous.IsValid; _currentImportReturnRip = previous.ReturnRip; _currentImportResumeRsp = previous.ResumeRsp; + _currentImportReturnSlotAddress = previous.ReturnSlotAddress; } public static bool TryGetCurrentImportCallFrame(out GuestImportCallFrame frame) @@ -188,7 +218,11 @@ public static class GuestThreadExecution return false; } - frame = new GuestImportCallFrame(true, _currentImportReturnRip, _currentImportResumeRsp); + frame = new GuestImportCallFrame( + true, + _currentImportReturnRip, + _currentImportResumeRsp, + _currentImportReturnSlotAddress); return true; } } diff --git a/src/SharpEmu.Libs/Ampr/AmprExports.cs b/src/SharpEmu.Libs/Ampr/AmprExports.cs index b6af3d7..ec9fe06 100644 --- a/src/SharpEmu.Libs/Ampr/AmprExports.cs +++ b/src/SharpEmu.Libs/Ampr/AmprExports.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later using SharpEmu.HLE; +using SharpEmu.Libs.Kernel; using System.Buffers.Binary; using System.Collections.Concurrent; @@ -17,8 +18,10 @@ public static class AmprExports private const ulong CommandBufferAux1Offset = 0x20; private const ulong ReadFileRecordSize = 0x30; private const ulong KernelEventQueueRecordSize = 0x30; + private const ulong WriteAddressRecordSize = 0x20; private const uint ReadFileRecordType = 1; private const uint KernelEventQueueRecordType = 2; + private const uint WriteAddressRecordType = 3; private static readonly ConcurrentDictionary _commandBuffers = new(); private sealed class CommandBufferState @@ -284,6 +287,18 @@ public static class AmprExports return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + [SysAbiExport( + Nid = "C+IEj+BsAFM", + ExportName = "sceAmprMeasureCommandSizeWriteAddressOnCompletion", + Target = Generation.Gen5, + LibraryName = "libSceAmpr")] + public static int MeasureCommandSizeWriteAddressOnCompletion(CpuContext ctx) + { + TraceAmpr(ctx, "measure_write_address_complete", 0, WriteAddressRecordSize, 0); + ctx[CpuRegister.Rax] = WriteAddressRecordSize; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + [SysAbiExport( Nid = "tZDDEo2tE5k", ExportName = "sceAmprCommandBufferGetSize", @@ -362,6 +377,92 @@ public static class AmprExports return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + [SysAbiExport( + Nid = "sJXyWHjP-F8", + ExportName = "sceAmprCommandBufferWriteAddressOnCompletion", + Target = Generation.Gen5, + LibraryName = "libSceAmpr")] + public static int CommandBufferWriteAddressOnCompletion(CpuContext ctx) + { + var commandBuffer = ctx[CpuRegister.Rdi]; + var address = ctx[CpuRegister.Rsi]; + var value = ctx[CpuRegister.Rdx]; + + if (commandBuffer == 0 || address == 0) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + } + + if (!AppendWriteAddressRecord(ctx, commandBuffer, address, value)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + TraceAmpr(ctx, "write_address_complete", commandBuffer, address, value); + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + public static int CompleteCommandBuffer(CpuContext ctx, ulong commandBuffer) + { + if (commandBuffer == 0) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + } + + if (!TryGetCommandBufferState(ctx, commandBuffer, out var buffer, out _, out var state) || state is null) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + ulong writeOffset; + lock (state) + { + writeOffset = state.WriteOffset; + } + + var offset = 0UL; + while (offset < writeOffset) + { + if (!TryReadUInt32(ctx, buffer + offset, out var recordType)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + switch (recordType) + { + case ReadFileRecordType: + offset += ReadFileRecordSize; + break; + + case KernelEventQueueRecordType: + if (!CompleteKernelEventQueueRecord(ctx, buffer + offset)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + offset += KernelEventQueueRecordSize; + break; + + case WriteAddressRecordType: + if (!CompleteWriteAddressRecord(ctx, buffer + offset)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + offset += WriteAddressRecordSize; + break; + + default: + TraceAmpr(ctx, "complete_unknown", commandBuffer, recordType, offset); + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + } + } + + TraceAmpr(ctx, "complete", commandBuffer, buffer, writeOffset); + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + private static bool InitializeCommandBuffer( CpuContext ctx, ulong commandBuffer, @@ -576,6 +677,17 @@ public static class AmprExports return AppendCommandBufferRecord(ctx, commandBuffer, record); } + private static bool AppendWriteAddressRecord(CpuContext ctx, ulong commandBuffer, ulong address, ulong value) + { + Span record = stackalloc byte[(int)WriteAddressRecordSize]; + record.Clear(); + BinaryPrimitives.WriteUInt32LittleEndian(record[0x00..], WriteAddressRecordType); + BinaryPrimitives.WriteUInt64LittleEndian(record[0x08..], address); + BinaryPrimitives.WriteUInt64LittleEndian(record[0x10..], value); + + return AppendCommandBufferRecord(ctx, commandBuffer, record); + } + private static bool AppendCommandBufferRecord(CpuContext ctx, ulong commandBuffer, ReadOnlySpan record) { if (!TryGetCommandBufferState(ctx, commandBuffer, out _, out _, out var state) || state is null) @@ -604,6 +716,66 @@ public static class AmprExports return true; } + private static bool CompleteKernelEventQueueRecord(CpuContext ctx, ulong recordAddress) + { + Span record = stackalloc byte[(int)KernelEventQueueRecordSize]; + if (!ctx.Memory.TryRead(recordAddress, record)) + { + return false; + } + + var filter = unchecked((short)BinaryPrimitives.ReadUInt32LittleEndian(record[0x04..])); + var equeue = BinaryPrimitives.ReadUInt64LittleEndian(record[0x08..]); + var ident = BinaryPrimitives.ReadUInt64LittleEndian(record[0x10..]); + var userData = BinaryPrimitives.ReadUInt64LittleEndian(record[0x18..]); + var data = BinaryPrimitives.ReadUInt64LittleEndian(record[0x20..]); + var extra = BinaryPrimitives.ReadUInt64LittleEndian(record[0x28..]); + + var queuedEvent = new KernelEventQueueCompatExports.KernelQueuedEvent( + ident, + filter, + 0x20, + unchecked((uint)extra), + data, + userData); + + _ = KernelEventQueueCompatExports.EnqueueEvent(equeue, queuedEvent); + TraceAmpr(ctx, "complete_equeue", equeue, ident, data); + return true; + } + + private static bool CompleteWriteAddressRecord(CpuContext ctx, ulong recordAddress) + { + Span record = stackalloc byte[(int)WriteAddressRecordSize]; + if (!ctx.Memory.TryRead(recordAddress, record)) + { + return false; + } + + var address = BinaryPrimitives.ReadUInt64LittleEndian(record[0x08..]); + var value = BinaryPrimitives.ReadUInt64LittleEndian(record[0x10..]); + if (!ctx.TryWriteUInt64(address, value)) + { + return false; + } + + TraceAmpr(ctx, "complete_write_address", address, value, 0); + return true; + } + + private static bool TryReadUInt32(CpuContext ctx, ulong address, out uint value) + { + Span buffer = stackalloc byte[sizeof(uint)]; + if (!ctx.Memory.TryRead(address, buffer)) + { + value = 0; + return false; + } + + value = BinaryPrimitives.ReadUInt32LittleEndian(buffer); + return true; + } + private static void TraceAmpr(CpuContext ctx, string operation, ulong commandBuffer, ulong arg0, ulong arg1) { if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AMPR"), "1", StringComparison.Ordinal)) diff --git a/src/SharpEmu.Libs/Fiber/FiberExports.cs b/src/SharpEmu.Libs/Fiber/FiberExports.cs index 8e7dcfc..db56033 100644 --- a/src/SharpEmu.Libs/Fiber/FiberExports.cs +++ b/src/SharpEmu.Libs/Fiber/FiberExports.cs @@ -230,9 +230,9 @@ public static class FiberExports LibraryName = "libSceFiber")] public static int FiberReturnToThread(CpuContext ctx) { - var fiberAddress = _currentFiberAddress; + var fiberAddress = ResolveCurrentFiberAddress(ctx); var inferredFiber = false; - if (fiberAddress == 0 && TryFindFiberByStack(ctx, out fiberAddress)) + if (_currentFiberAddress == 0 && fiberAddress != 0) { inferredFiber = true; } @@ -258,9 +258,9 @@ public static class FiberExports if (GuestThreadExecution.TryGetCurrentImportCallFrame(out var frame)) { _continuations[fiberAddress] = new FiberContinuation( - CaptureContinuation(ctx, frame.ReturnRip, frame.ResumeRsp), + CaptureContinuation(ctx, frame.ReturnRip, frame.ResumeRsp, frame.ReturnSlotAddress), argOnRunAddress); - TraceFiber($"yield{(inferredFiber ? "-inferred" : string.Empty)} fiber=0x{fiberAddress:X16} resume=0x{frame.ReturnRip:X16} rsp=0x{frame.ResumeRsp:X16} arg_out=0x{argOnRunAddress:X16}"); + TraceFiber($"yield{(inferredFiber ? "-inferred" : string.Empty)} fiber=0x{fiberAddress:X16} resume=0x{frame.ReturnRip:X16} rsp=0x{frame.ResumeRsp:X16} return_slot=0x{frame.ReturnSlotAddress:X16} arg_out=0x{argOnRunAddress:X16}"); } else { @@ -284,12 +284,13 @@ public static class FiberExports return SetReturn(ctx, FiberErrorNull); } - if (_currentFiberAddress == 0) + var fiberAddress = ResolveCurrentFiberAddress(ctx); + if (fiberAddress == 0) { return SetReturn(ctx, FiberErrorPermission); } - return TryWriteUInt64(ctx, outAddress, _currentFiberAddress) + return TryWriteUInt64(ctx, outAddress, fiberAddress) ? SetReturn(ctx, 0) : SetReturn(ctx, FiberErrorInvalid); } @@ -407,7 +408,7 @@ public static class FiberExports return SetReturn(ctx, FiberErrorNull); } - if (_currentFiberAddress == 0) + if (ResolveCurrentFiberAddress(ctx) == 0) { return SetReturn(ctx, FiberErrorPermission); } @@ -537,10 +538,11 @@ public static class FiberExports if (fields.State != FiberStateIdle) { + TraceFiber($"run-state-error reason={reason} fiber=0x{fiber:X16} state=0x{fields.State:X8}"); return SetReturn(ctx, FiberErrorState); } - var previousFiber = _currentFiberAddress; + var previousFiber = ResolveCurrentFiberAddress(ctx); var switchingFromFiber = isSwitch && previousFiber != 0 && previousFiber != fiber; if (isSwitch && previousFiber == 0) { @@ -553,56 +555,111 @@ public static class FiberExports return SetReturn(ctx, FiberErrorPermission); } + var session = new FiberRunSession(); + var ownsSession = _runSessions.TryAdd(fiber, session); + if (!ownsSession && !_runSessions.TryGetValue(fiber, out session)) + { + return SetReturn(ctx, FiberErrorState); + } + + FiberContinuation? previousContinuation = null; + GuestImportCallFrame switchFrame = default; + if (switchingFromFiber) + { + if (!GuestThreadExecution.TryGetCurrentImportCallFrame(out switchFrame)) + { + if (ownsSession) + { + _runSessions.TryRemove(fiber, out _); + } + TraceFiber($"switch-no-frame from=0x{previousFiber:X16} to=0x{fiber:X16} arg_out=0x{outArgumentAddress:X16}"); + return SetReturn(ctx, FiberErrorPermission); + } + + previousContinuation = new FiberContinuation( + CaptureContinuation(ctx, switchFrame.ReturnRip, switchFrame.ResumeRsp, switchFrame.ReturnSlotAddress), + outArgumentAddress); + } + if (!TryWriteUInt32(ctx, fiber + FiberStateOffset, FiberStateRun)) { + if (ownsSession) + { + _runSessions.TryRemove(fiber, out _); + } return SetReturn(ctx, FiberErrorInvalid); } if (switchingFromFiber && !TryWriteUInt32(ctx, previousFiber + FiberStateOffset, FiberStateIdle)) { _ = TryWriteUInt32(ctx, fiber + FiberStateOffset, FiberStateIdle); + if (ownsSession) + { + _runSessions.TryRemove(fiber, out _); + } return SetReturn(ctx, FiberErrorInvalid); } + if (previousContinuation.HasValue) + { + _continuations[previousFiber] = previousContinuation.Value; + TraceFiber($"switch-save from=0x{previousFiber:X16} to=0x{fiber:X16} resume=0x{switchFrame.ReturnRip:X16} rsp=0x{switchFrame.ResumeRsp:X16} return_slot=0x{switchFrame.ReturnSlotAddress:X16} arg_out=0x{outArgumentAddress:X16}"); + } + var previousReturnRequested = _fiberReturnRequested; var previousReturnArgument = _fiberReturnArgument; - var session = new FiberRunSession(); - _runSessions[fiber] = session; _currentFiberAddress = fiber; _fiberReturnRequested = false; _fiberReturnArgument = 0; + var previousExecutionFiber = GuestThreadExecution.EnterFiber(fiber); + TraceFiber($"run-enter reason={reason} fiber=0x{fiber:X16} prev=0x{previousFiber:X16} thread=0x{GuestThreadExecution.CurrentGuestThreadHandle:X16} managed={Environment.CurrentManagedThreadId} stack=0x{fields.ContextAddress:X16}"); var hasContinuation = _continuations.TryGetValue(fiber, out var continuation); - bool callbackOk; - string? callbackError; if (hasContinuation) { - if (continuation.ArgOnRunAddress != 0 && - !TryWriteUInt64(ctx, continuation.ArgOnRunAddress, argOnRun)) + _continuations.TryRemove(fiber, out _); + } + + TraceFiber(hasContinuation + ? $"run-dispatch reason={reason} fiber=0x{fiber:X16} resume=1 rip=0x{continuation.Context.Rip:X16} rsp=0x{continuation.Context.Rsp:X16} return_slot=0x{continuation.Context.ReturnSlotAddress:X16} arg_out=0x{continuation.ArgOnRunAddress:X16}" + : $"run-dispatch reason={reason} fiber=0x{fiber:X16} resume=0 rip=0x{fields.Entry:X16} rsp=0x{fields.ContextAddress + fields.ContextSize:X16} arg_out=0x{outArgumentAddress:X16}"); + bool callbackOk; + string? callbackError; + try + { + if (hasContinuation) { - callbackOk = false; - callbackError = $"failed to write resumed argOnRun to 0x{continuation.ArgOnRunAddress:X16}"; + if (continuation.ArgOnRunAddress != 0 && + !TryWriteUInt64(ctx, continuation.ArgOnRunAddress, argOnRun)) + { + callbackOk = false; + callbackError = $"failed to write resumed argOnRun to 0x{continuation.ArgOnRunAddress:X16}"; + } + else + { + callbackOk = scheduler.TryCallGuestContinuation( + ctx, + continuation.Context, + reason, + out callbackError); + } } else { - callbackOk = scheduler.TryCallGuestContinuation( + callbackOk = scheduler.TryCallGuestFunction( ctx, - continuation.Context, + fields.Entry, + fields.ArgOnInitialize, + argOnRun, + fields.ContextAddress, + fields.ContextSize, reason, out callbackError); } } - else + finally { - callbackOk = scheduler.TryCallGuestFunction( - ctx, - fields.Entry, - fields.ArgOnInitialize, - argOnRun, - fields.ContextAddress, - fields.ContextSize, - reason, - out callbackError); + GuestThreadExecution.RestoreFiber(previousExecutionFiber); } var returnRequested = _fiberReturnRequested; @@ -613,11 +670,10 @@ public static class FiberExports returnArgument = sessionReturnArgument; } - if (!returnRequested) + if (ownsSession) { - _continuations.TryRemove(fiber, out _); + _runSessions.TryRemove(fiber, out _); } - _runSessions.TryRemove(fiber, out _); _currentFiberAddress = previousFiber; _fiberReturnRequested = previousReturnRequested; @@ -644,10 +700,15 @@ public static class FiberExports return SetReturn(ctx, 0); } - private static GuestCpuContinuation CaptureContinuation(CpuContext ctx, ulong resumeRip, ulong resumeRsp) => + private static GuestCpuContinuation CaptureContinuation( + CpuContext ctx, + ulong resumeRip, + ulong resumeRsp, + ulong returnSlotAddress) => new( resumeRip, resumeRsp, + returnSlotAddress, ctx.Rflags == 0 ? 0x202UL : ctx.Rflags, ctx.FsBase, ctx.GsBase, @@ -665,6 +726,24 @@ public static class FiberExports ctx[CpuRegister.R14], ctx[CpuRegister.R15]); + private static ulong ResolveCurrentFiberAddress(CpuContext ctx) + { + if (_currentFiberAddress != 0) + { + return _currentFiberAddress; + } + + if (GuestThreadExecution.CurrentFiberAddress != 0) + { + return GuestThreadExecution.CurrentFiberAddress; + } + + return TryFindFiberByStack(ctx, out var fiberAddress) ? fiberAddress : 0; + } + + internal static ulong GetCurrentFiberAddressForDiagnostics(CpuContext ctx) => + ResolveCurrentFiberAddress(ctx); + private static int AttachContext( CpuContext ctx, ulong fiber, diff --git a/src/SharpEmu.Libs/Kernel/KernelAprCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelAprCompatExports.cs index dce05b5..379f919 100644 --- a/src/SharpEmu.Libs/Kernel/KernelAprCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelAprCompatExports.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later using SharpEmu.HLE; +using SharpEmu.Libs.Ampr; using System.Buffers.Binary; using System.Collections.Concurrent; using System.Threading; @@ -38,6 +39,12 @@ public static class KernelAprCompatExports _submittedCommandBuffers[submissionId] = commandBuffer; + var completionResult = AmprExports.CompleteCommandBuffer(ctx, commandBuffer); + if (completionResult != (int)OrbisGen2Result.ORBIS_GEN2_OK) + { + return completionResult; + } + if (outSubmissionId != 0 && !TryWriteUInt32(ctx, outSubmissionId, submissionId)) { return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; @@ -93,6 +100,12 @@ public static class KernelAprCompatExports var submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId)); _submittedCommandBuffers[submissionId] = commandBuffer; + var completionResult = AmprExports.CompleteCommandBuffer(ctx, commandBuffer); + if (completionResult != (int)OrbisGen2Result.ORBIS_GEN2_OK) + { + return completionResult; + } + TraceApr(ctx, "submit", submissionId, commandBuffer, ctx[CpuRegister.Rsi], 0); return (int)OrbisGen2Result.ORBIS_GEN2_OK; } @@ -114,6 +127,12 @@ public static class KernelAprCompatExports var submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId)); _submittedCommandBuffers[submissionId] = commandBuffer; + var completionResult = AmprExports.CompleteCommandBuffer(ctx, commandBuffer); + if (completionResult != (int)OrbisGen2Result.ORBIS_GEN2_OK) + { + return completionResult; + } + if (!TryWriteUInt32(ctx, outSubmissionId, submissionId)) { return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; @@ -154,5 +173,12 @@ public static class KernelAprCompatExports _ = ctx.TryReadUInt64(ctx[CpuRegister.Rsp], out returnRip); Console.Error.WriteLine( $"[LOADER][TRACE] apr.{operation}: id=0x{submissionId:X8} cmd=0x{commandBuffer:X16} priority=0x{priority:X16} aux=0x{aux:X16} ret=0x{returnRip:X16}"); + if (aux != 0 && + ctx.TryReadUInt64(aux, out var result0) && + ctx.TryReadUInt64(aux + sizeof(ulong), out var result1)) + { + Console.Error.WriteLine( + $"[LOADER][TRACE] apr.{operation}.result: addr=0x{aux:X16} q0=0x{result0:X16} q1=0x{result1:X16}"); + } } } diff --git a/src/SharpEmu.Libs/Kernel/KernelEventFlagCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelEventFlagCompatExports.cs index 3e583a2..877a819 100644 --- a/src/SharpEmu.Libs/Kernel/KernelEventFlagCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelEventFlagCompatExports.cs @@ -5,6 +5,7 @@ using System.Buffers.Binary; using System.Collections.Concurrent; using System.Text; using SharpEmu.HLE; +using SharpEmu.Libs.Fiber; namespace SharpEmu.Libs.Kernel; @@ -113,6 +114,7 @@ public static class KernelEventFlagCompatExports { var handle = ctx[CpuRegister.Rdi]; var pattern = ctx[CpuRegister.Rsi]; + var returnRip = GetCurrentReturnRip(); if (!_eventFlags.TryGetValue(handle, out var state)) { return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND); @@ -122,7 +124,7 @@ public static class KernelEventFlagCompatExports { state.Bits |= pattern; Monitor.PulseAll(state.Gate); - TraceEventFlag($"set handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16}"); + TraceEventFlag($"set handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} ret=0x{returnRip:X16}"); } return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK); @@ -203,6 +205,7 @@ public static class KernelEventFlagCompatExports var waitMode = unchecked((uint)ctx[CpuRegister.Rdx]); var resultAddress = ctx[CpuRegister.Rcx]; var timeoutAddress = ctx[CpuRegister.R8]; + var returnRip = GetCurrentReturnRip(); if (!_eventFlags.TryGetValue(handle, out var state)) { @@ -232,11 +235,17 @@ public static class KernelEventFlagCompatExports { _ = TryWriteUInt32(ctx, timeoutAddress, 0); _ = TryWriteResultPattern(ctx, resultAddress, state.Bits); - TraceEventFlag($"wait-timeout handle=0x{handle:X16} pattern=0x{pattern:X16} timeout={timeoutUsec}"); + TraceEventFlag($"wait-timeout handle=0x{handle:X16} pattern=0x{pattern:X16} timeout={timeoutUsec} ret=0x{returnRip:X16}"); return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT); } - if (!GuestThreadExecution.RequestCurrentThreadBlock("sceKernelWaitEventFlag")) + var currentGuestThread = GuestThreadExecution.CurrentGuestThreadHandle; + var currentFiber = FiberExports.GetCurrentFiberAddressForDiagnostics(ctx); + var managedThread = Environment.CurrentManagedThreadId; + var requestedBlock = GuestThreadExecution.RequestCurrentThreadBlock("sceKernelWaitEventFlag"); + TraceEventFlag($"wait-unsatisfied handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} guest_thread=0x{currentGuestThread:X16} fiber=0x{currentFiber:X16} managed={managedThread} block={requestedBlock} ret=0x{returnRip:X16} frames={FormatFrameChain(ctx)}"); + TraceEventFlag($"wait-object handle=0x{handle:X16} name='{state.Name}' {FormatGuestWaitObject(ctx)}"); + if (!requestedBlock) { var scheduler = GuestThreadExecution.Scheduler; if (scheduler is null) @@ -245,7 +254,7 @@ public static class KernelEventFlagCompatExports } state.WaitingThreads++; - TraceEventFlag($"wait-pump handle=0x{handle:X16} pattern=0x{pattern:X16} waiters={state.WaitingThreads}"); + TraceEventFlag($"wait-pump handle=0x{handle:X16} pattern=0x{pattern:X16} waiters={state.WaitingThreads} guest_thread=0x{currentGuestThread:X16} fiber=0x{currentFiber:X16} managed={managedThread} ret=0x{returnRip:X16}"); var releaseWaiter = true; try { @@ -265,7 +274,7 @@ public static class KernelEventFlagCompatExports { state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1); releaseWaiter = false; - TraceEventFlag($"wait-wake handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} waiters={state.WaitingThreads}"); + TraceEventFlag($"wait-wake handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} waiters={state.WaitingThreads} ret=0x{returnRip:X16}"); return SetReturn(ctx, pumpedWaitResult); } @@ -282,7 +291,7 @@ public static class KernelEventFlagCompatExports } state.WaitingThreads++; - TraceEventFlag($"wait-block handle=0x{handle:X16} pattern=0x{pattern:X16} waiters={state.WaitingThreads}"); + TraceEventFlag($"wait-block handle=0x{handle:X16} pattern=0x{pattern:X16} waiters={state.WaitingThreads} guest_thread=0x{currentGuestThread:X16} fiber=0x{currentFiber:X16} managed={managedThread} ret=0x{returnRip:X16}"); return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK); } finally @@ -398,6 +407,32 @@ public static class KernelEventFlagCompatExports return true; } + private static bool TryReadUInt64(CpuContext ctx, ulong address, out ulong value) + { + Span buffer = stackalloc byte[sizeof(ulong)]; + if (!ctx.Memory.TryRead(address, buffer)) + { + value = 0; + return false; + } + + value = BinaryPrimitives.ReadUInt64LittleEndian(buffer); + return true; + } + + private static bool TryReadByte(CpuContext ctx, ulong address, out byte value) + { + Span buffer = stackalloc byte[1]; + if (!ctx.Memory.TryRead(address, buffer)) + { + value = 0; + return false; + } + + value = buffer[0]; + return true; + } + private static bool TryWriteUInt32(CpuContext ctx, ulong address, uint value) { Span buffer = stackalloc byte[sizeof(uint)]; @@ -444,4 +479,101 @@ public static class KernelEventFlagCompatExports Console.Error.WriteLine($"[LOADER][TRACE] event_flag.{message}"); } } + + private static ulong GetCurrentReturnRip() => + GuestThreadExecution.TryGetCurrentImportCallFrame(out var frame) + ? frame.ReturnRip + : 0UL; + + private static string FormatFrameChain(CpuContext ctx) + { + Span returns = stackalloc ulong[4]; + var count = 0; + var frame = ctx[CpuRegister.Rbp]; + for (var index = 0; index < returns.Length && frame != 0; index++) + { + if (!ctx.TryReadUInt64(frame, out var nextFrame) || + !ctx.TryReadUInt64(frame + sizeof(ulong), out var returnAddress)) + { + break; + } + + returns[count++] = returnAddress; + if (nextFrame <= frame) + { + break; + } + + frame = nextFrame; + } + + return count switch + { + 0 => "none", + 1 => $"0x{returns[0]:X16}", + 2 => $"0x{returns[0]:X16},0x{returns[1]:X16}", + 3 => $"0x{returns[0]:X16},0x{returns[1]:X16},0x{returns[2]:X16}", + _ => $"0x{returns[0]:X16},0x{returns[1]:X16},0x{returns[2]:X16},0x{returns[3]:X16}", + }; + } + + private static string FormatGuestWaitObject(CpuContext ctx) + { + var r12 = ctx[CpuRegister.R12]; + var r13 = ctx[CpuRegister.R13]; + var objectAddress = r12 != 0 + ? r12 + : r13 >= 0xA8 + ? r13 - 0xA8 + : 0; + + var builder = new StringBuilder(256); + builder.Append($"r12=0x{r12:X16} r13=0x{r13:X16}"); + if (objectAddress == 0) + { + return builder.ToString(); + } + + builder.Append($" obj=0x{objectAddress:X16}"); + AppendUInt32(builder, ctx, objectAddress + 0x58, "o58"); + AppendUInt32(builder, ctx, objectAddress + 0x5C, "o5C"); + AppendUInt64(builder, ctx, objectAddress + 0x60, "o60"); + AppendByte(builder, ctx, objectAddress + 0x6C, "state6C"); + AppendByte(builder, ctx, objectAddress + 0x6D, "o6D"); + AppendByte(builder, ctx, objectAddress + 0xA0, "waitA0"); + AppendByte(builder, ctx, objectAddress + 0xA1, "stateA1"); + AppendByte(builder, ctx, objectAddress + 0xA2, "oA2"); + AppendUInt64(builder, ctx, objectAddress + 0xA8, "eventA8"); + if (r13 != 0) + { + AppendUInt64(builder, ctx, r13, "r13_0"); + AppendUInt64(builder, ctx, r13 + 8, "r13_8"); + } + + return builder.ToString(); + } + + private static void AppendByte(StringBuilder builder, CpuContext ctx, ulong address, string name) + { + if (TryReadByte(ctx, address, out var value)) + { + builder.Append($" {name}=0x{value:X2}"); + } + } + + private static void AppendUInt32(StringBuilder builder, CpuContext ctx, ulong address, string name) + { + if (TryReadUInt32(ctx, address, out var value)) + { + builder.Append($" {name}=0x{value:X8}"); + } + } + + private static void AppendUInt64(StringBuilder builder, CpuContext ctx, ulong address, string name) + { + if (TryReadUInt64(ctx, address, out var value)) + { + builder.Append($" {name}=0x{value:X16}"); + } + } } diff --git a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs index 275f081..9233877 100644 --- a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs @@ -3,6 +3,7 @@ using SharpEmu.HLE; using SharpEmu.Libs.Ampr; +using System.Buffers; using System.Buffers.Binary; using System.Text; using System.Threading; @@ -164,7 +165,7 @@ public static class KernelMemoryCompatExports var desiredAddress = AlignUp( _nextVirtualAddress == 0 ? 0x1_0000_0000UL : _nextVirtualAddress, effectiveAlignment); - if (!TryReserveGuestVirtualRange(ctx, desiredAddress, mappedLength, OrbisProtCpuReadWrite, out address) || + if (!TryReserveGuestVirtualRange(ctx, desiredAddress, mappedLength, OrbisProtCpuReadWrite, effectiveAlignment, out address) || address == 0) { return false; @@ -597,7 +598,6 @@ public static class KernelMemoryCompatExports ulong NextGpArg() => vaCursor.NextGpArg(); double NextFloatArg() => vaCursor.NextFloatArg(); rendered = FormatString(ctx, format, NextGpArg, NextFloatArg); - vaCursor.Commit(); } Console.Write(rendered); @@ -2242,7 +2242,7 @@ public static class KernelMemoryCompatExports } else { - reserved = TryReserveGuestVirtualRange(ctx, desiredAddress, length, protection, out mappedAddress); + reserved = TryReserveGuestVirtualRange(ctx, desiredAddress, length, protection, effectiveAlignment, out mappedAddress); } if (ShouldTraceDirectMemory()) { @@ -2331,7 +2331,7 @@ public static class KernelMemoryCompatExports { mappedAddress = requestedAddress; } - else if (!TryReserveGuestVirtualRange(ctx, desiredAddress, length, protection, out mappedAddress)) + else if (!TryReserveGuestVirtualRange(ctx, desiredAddress, length, protection, OrbisPageSize, out mappedAddress)) { mappedAddress = requestedAddress != 0 && fixedMapping ? requestedAddress @@ -2721,7 +2721,6 @@ public static class KernelMemoryCompatExports ulong NextGpArg() => vaCursor.NextGpArg(); double NextFloatArg() => vaCursor.NextFloatArg(); var rendered = FormatString(ctx, format, NextGpArg, NextFloatArg); - vaCursor.Commit(); var outputBytes = Encoding.UTF8.GetBytes(rendered); return WriteSnprintfOutput(ctx, destination, bufferSize, outputBytes); @@ -2768,7 +2767,6 @@ public static class KernelMemoryCompatExports ulong NextGpArg() => vaCursor.NextGpArg(); double NextFloatArg() => vaCursor.NextFloatArg(); rendered = FormatString(ctx, format, NextGpArg, NextFloatArg); - vaCursor.Commit(); } TraceWidePrintf(ctx, "vswprintf", destination, bufferSize, format, rendered); @@ -3552,13 +3550,6 @@ public static class KernelMemoryCompatExports : 0.0; } - public void Commit() - { - _ = TryWriteUInt32Compat(_ctx, _vaListAddress + 0, _gpOffset); - _ = TryWriteUInt32Compat(_ctx, _vaListAddress + 4, _fpOffset); - _ = TryWriteUInt64Compat(_ctx, _vaListAddress + 8, _overflowArgArea); - } - public uint GpOffset => _gpOffset; public uint FpOffset => _fpOffset; @@ -3613,6 +3604,7 @@ public static class KernelMemoryCompatExports ulong desiredAddress, ulong length, int protection, + ulong alignment, out ulong mappedAddress) { mappedAddress = 0; @@ -3625,40 +3617,51 @@ public static class KernelMemoryCompatExports { object memoryObject = ctx.Memory; MethodInfo? allocateAt = null; + MethodInfo? allocateAtOrAbove = null; var allocateAtHasAllowAlternativeArg = false; for (var depth = 0; depth < 4; depth++) { foreach (var candidate in memoryObject.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)) { - if (!string.Equals(candidate.Name, "AllocateAt", StringComparison.Ordinal)) - { - continue; - } - var parameters = candidate.GetParameters(); - if (parameters.Length == 3 && - parameters[0].ParameterType == typeof(ulong) && - parameters[1].ParameterType == typeof(ulong) && - parameters[2].ParameterType == typeof(bool)) - { - allocateAt = candidate; - allocateAtHasAllowAlternativeArg = false; - break; - } - - if (parameters.Length == 4 && + if (string.Equals(candidate.Name, "TryAllocateAtOrAbove", StringComparison.Ordinal) && + parameters.Length == 5 && parameters[0].ParameterType == typeof(ulong) && parameters[1].ParameterType == typeof(ulong) && parameters[2].ParameterType == typeof(bool) && - parameters[3].ParameterType == typeof(bool)) + parameters[3].ParameterType == typeof(ulong) && + parameters[4].ParameterType == typeof(ulong).MakeByRefType()) + { + allocateAtOrAbove = candidate; + } + else if (string.Equals(candidate.Name, "AllocateAt", StringComparison.Ordinal)) + { + if (parameters.Length == 3 && + parameters[0].ParameterType == typeof(ulong) && + parameters[1].ParameterType == typeof(ulong) && + parameters[2].ParameterType == typeof(bool)) + { + allocateAt = candidate; + allocateAtHasAllowAlternativeArg = false; + } + else if (parameters.Length == 4 && + parameters[0].ParameterType == typeof(ulong) && + parameters[1].ParameterType == typeof(ulong) && + parameters[2].ParameterType == typeof(bool) && + parameters[3].ParameterType == typeof(bool)) + { + allocateAt = candidate; + allocateAtHasAllowAlternativeArg = true; + } + } + + if (allocateAtOrAbove is not null && allocateAt is not null) { - allocateAt = candidate; - allocateAtHasAllowAlternativeArg = true; break; } } - if (allocateAt is not null) + if (allocateAtOrAbove is not null || allocateAt is not null) { break; } @@ -3678,15 +3681,27 @@ public static class KernelMemoryCompatExports memoryObject = innerValue; } + var executable = (protection & OrbisProtCpuExec) != 0; + if (allocateAtOrAbove is not null) + { + var searchArgs = new object[] { desiredAddress, length, executable, alignment, 0UL }; + var searchResult = allocateAtOrAbove.Invoke(memoryObject, searchArgs); + if (searchResult is bool trueValue && trueValue && + searchArgs[4] is ulong searchedAddress && searchedAddress != 0) + { + mappedAddress = searchedAddress; + return true; + } + } + if (allocateAt is null) { Console.Error.WriteLine($"[LOADER][TRACE] reserve range: AllocateAt missing on {ctx.Memory.GetType().FullName}"); return false; } - var executable = (protection & OrbisProtCpuExec) != 0; var invokeArgs = allocateAtHasAllowAlternativeArg - ? new object[] { desiredAddress, length, executable, true } + ? new object[] { desiredAddress, length, executable, false } : new object[] { desiredAddress, length, executable }; var result = allocateAt.Invoke(memoryObject, invokeArgs); if (result is not ulong allocated || allocated == 0) @@ -3959,6 +3974,16 @@ public static class KernelMemoryCompatExports } private static bool TryReadCString(CpuContext ctx, ulong address, ulong maxLength, out byte[] bytes) + { + return TryReadBytesUntilNull(ctx, address, maxLength, 1_048_576, out bytes); + } + + private static bool TryReadBytesUntilNull( + CpuContext ctx, + ulong address, + ulong maxLength, + int hardLimit, + out byte[] bytes) { bytes = Array.Empty(); if (address == 0) @@ -3966,26 +3991,65 @@ public static class KernelMemoryCompatExports return false; } - var limit = (int)Math.Min(maxLength, 1_048_576UL); - var buffer = new List(Math.Min(limit, 256)); - Span one = stackalloc byte[1]; - for (var i = 0; i < limit; i++) + var limit = (int)Math.Min(maxLength, (ulong)Math.Max(0, hardLimit)); + if (limit == 0) { - if (!TryReadCompat(ctx, address + (ulong)i, one)) + return true; + } + + const int maxChunkSize = 4096; + var chunk = GC.AllocateUninitializedArray(Math.Min(maxChunkSize, limit)); + var writer = new ArrayBufferWriter(Math.Min(limit, 256)); + ulong offset = 0; + while (offset < (ulong)limit) + { + var current = address + offset; + if (current < address) + { + return false; + } + + var pageRemaining = maxChunkSize - (int)(current & (maxChunkSize - 1)); + var remaining = (int)Math.Min((ulong)limit - offset, (ulong)Math.Min(chunk.Length, pageRemaining)); + var span = chunk.AsSpan(0, remaining); + if (TryReadCompat(ctx, current, span)) + { + var nulIndex = span.IndexOf((byte)0); + var copyLength = nulIndex >= 0 ? nulIndex : remaining; + if (copyLength > 0) + { + span[..copyLength].CopyTo(writer.GetSpan(copyLength)); + writer.Advance(copyLength); + } + + if (nulIndex >= 0) + { + bytes = writer.WrittenSpan.ToArray(); + return true; + } + + offset += (ulong)remaining; + continue; + } + + Span one = stackalloc byte[1]; + if (!TryReadCompat(ctx, current, one)) { return false; } if (one[0] == 0) { - bytes = buffer.ToArray(); + bytes = writer.WrittenSpan.ToArray(); return true; } - buffer.Add(one[0]); + one.CopyTo(writer.GetSpan(1)); + writer.Advance(1); + offset++; } - bytes = buffer.ToArray(); + bytes = writer.WrittenSpan.ToArray(); return true; } @@ -4165,25 +4229,12 @@ public static class KernelMemoryCompatExports return false; } - var buffer = new List(Math.Min(maxLength, 256)); - Span one = stackalloc byte[1]; - for (var i = 0; i < maxLength; i++) + if (!TryReadBytesUntilNull(ctx, address, (ulong)maxLength, maxLength, out var bytes)) { - if (!TryReadCompat(ctx, address + (ulong)i, one)) - { - return false; - } - - if (one[0] == 0) - { - value = Encoding.UTF8.GetString(buffer.ToArray()); - return true; - } - - buffer.Add(one[0]); + return false; } - value = Encoding.UTF8.GetString(buffer.ToArray()); + value = Encoding.UTF8.GetString(bytes); return true; } diff --git a/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs index c7cac09..eb7858e 100644 --- a/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later using SharpEmu.HLE; +using SharpEmu.Libs.Fiber; using System.Buffers.Binary; using System.Collections.Generic; using System.Diagnostics; @@ -32,6 +33,8 @@ public static class KernelRuntimeCompatExports private const ulong DefaultKernelTscFrequency = 10_000_000UL; private const ulong PrtAreaStartAddress = 0x0000001000000000UL; private const ulong PrtAreaSize = 0x000000EC00000000UL; + private const int MapFlagFixed = 0x10; + private const ulong DefaultVirtualRangeAlignment = 0x4000UL; private const int AioInitParamSize = 0x3C; private const uint MemCommit = 0x1000; private const uint MemReserve = 0x2000; @@ -73,6 +76,8 @@ public static class KernelRuntimeCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + GuestThreadExecution.Scheduler?.Pump(ctx, "sceKernelUsleep"); + if (micros < 1000) { Thread.Yield(); @@ -108,8 +113,14 @@ public static class KernelRuntimeCompatExports lockText = $"0x{lockValue:X16}"; } + var returnRip = GuestThreadExecution.TryGetCurrentImportCallFrame(out var frame) + ? frame.ReturnRip + : 0UL; + var thread = GuestThreadExecution.CurrentGuestThreadHandle; + var fiber = FiberExports.GetCurrentFiberAddressForDiagnostics(ctx); + Console.Error.WriteLine( - $"[LOADER][TRACE] usleep#{count}: usec={micros} rbx=0x{rbx:X16} lock@+F78=0x{lockAddress:X16}:{lockText} r13=0x{ctx[CpuRegister.R13]:X16} r14=0x{ctx[CpuRegister.R14]:X16} r15=0x{ctx[CpuRegister.R15]:X16}"); + $"[LOADER][TRACE] usleep#{count}: usec={micros} ret=0x{returnRip:X16} thread=0x{thread:X16} fiber=0x{fiber:X16} rbx=0x{rbx:X16} lock@+F78=0x{lockAddress:X16}:{lockText} r13=0x{ctx[CpuRegister.R13]:X16} r14=0x{ctx[CpuRegister.R14]:X16} r15=0x{ctx[CpuRegister.R15]:X16}"); } [SysAbiExport( @@ -653,7 +664,7 @@ public static class KernelRuntimeCompatExports { var inOutAddressPointer = ctx[CpuRegister.Rdi]; var length = ctx[CpuRegister.Rsi]; - var _flags = ctx[CpuRegister.Rdx]; + var flags = unchecked((int)ctx[CpuRegister.Rdx]); var alignment = ctx[CpuRegister.Rcx]; if (inOutAddressPointer == 0 || length == 0) { @@ -665,7 +676,8 @@ public static class KernelRuntimeCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; } - var effectiveAlignment = alignment == 0 ? 0x10000UL : alignment; + var effectiveAlignment = alignment == 0 ? DefaultVirtualRangeAlignment : alignment; + var fixedMapping = (flags & MapFlagFixed) != 0; ulong desiredAddress; lock (_stateGate) { @@ -674,13 +686,13 @@ public static class KernelRuntimeCompatExports : AlignUp(_nextReservedVirtualBase, effectiveAlignment); } - if (!TryReserveVirtualRange(ctx, desiredAddress, length, out var mappedAddress)) + if (!TryReserveVirtualRange(ctx, desiredAddress, length, effectiveAlignment, allowSearch: !fixedMapping, out var mappedAddress)) { return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; } Console.Error.WriteLine( - $"[LOADER][TRACE] reserve_virtual_range: req=0x{requestedAddress:X16} desired=0x{desiredAddress:X16} mapped=0x{mappedAddress:X16} len=0x{length:X16}"); + $"[LOADER][TRACE] reserve_virtual_range: req=0x{requestedAddress:X16} desired=0x{desiredAddress:X16} mapped=0x{mappedAddress:X16} len=0x{length:X16} flags=0x{flags:X8} align=0x{effectiveAlignment:X16}"); if (!ctx.TryWriteUInt64(inOutAddressPointer, mappedAddress)) { @@ -964,14 +976,12 @@ public static class KernelRuntimeCompatExports public static int StackCheckFail(CpuContext ctx) { var count = Interlocked.Increment(ref _stackChkFailCount); - if (count <= 8) - { - Console.Error.WriteLine( - $"[LOADER][WARNING] __stack_chk_fail recovery#{count}: rip=0x{ctx.Rip:X16} rdi=0x{ctx[CpuRegister.Rdi]:X16}"); - } - - ctx[CpuRegister.Rax] = 0; - return (int)OrbisGen2Result.ORBIS_GEN2_OK; + Console.Error.WriteLine( + $"[LOADER][ERROR] __stack_chk_fail#{count}: rip=0x{ctx.Rip:X16} rdi=0x{ctx[CpuRegister.Rdi]:X16}"); + var result = (int)OrbisGen2Result.ORBIS_GEN2_ERROR_CPU_TRAP; + GuestThreadExecution.RequestCurrentEntryExit("__stack_chk_fail", result); + ctx[CpuRegister.Rax] = unchecked((ulong)result); + return result; } [SysAbiExport( @@ -1656,7 +1666,13 @@ public static class KernelRuntimeCompatExports [DllImport("kernel32.dll", SetLastError = true)] private static extern nint VirtualAlloc(nint lpAddress, nuint dwSize, uint flAllocationType, uint flProtect); - private static bool TryReserveVirtualRange(CpuContext ctx, ulong desiredAddress, ulong length, out ulong mappedAddress) + private static bool TryReserveVirtualRange( + CpuContext ctx, + ulong desiredAddress, + ulong length, + ulong alignment, + bool allowSearch, + out ulong mappedAddress) { mappedAddress = 0; if (length == 0) @@ -1668,40 +1684,51 @@ public static class KernelRuntimeCompatExports { object memoryObject = ctx.Memory; MethodInfo? allocateAt = null; + MethodInfo? allocateAtOrAbove = null; var allocateAtHasAllowAlternativeArg = false; for (var depth = 0; depth < 4; depth++) { foreach (var candidate in memoryObject.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)) { - if (!string.Equals(candidate.Name, "AllocateAt", StringComparison.Ordinal)) - { - continue; - } - var parameters = candidate.GetParameters(); - if (parameters.Length == 3 && - parameters[0].ParameterType == typeof(ulong) && - parameters[1].ParameterType == typeof(ulong) && - parameters[2].ParameterType == typeof(bool)) - { - allocateAt = candidate; - allocateAtHasAllowAlternativeArg = false; - break; - } - - if (parameters.Length == 4 && + if (string.Equals(candidate.Name, "TryAllocateAtOrAbove", StringComparison.Ordinal) && + parameters.Length == 5 && parameters[0].ParameterType == typeof(ulong) && parameters[1].ParameterType == typeof(ulong) && parameters[2].ParameterType == typeof(bool) && - parameters[3].ParameterType == typeof(bool)) + parameters[3].ParameterType == typeof(ulong) && + parameters[4].ParameterType == typeof(ulong).MakeByRefType()) + { + allocateAtOrAbove = candidate; + } + else if (string.Equals(candidate.Name, "AllocateAt", StringComparison.Ordinal)) + { + if (parameters.Length == 3 && + parameters[0].ParameterType == typeof(ulong) && + parameters[1].ParameterType == typeof(ulong) && + parameters[2].ParameterType == typeof(bool)) + { + allocateAt = candidate; + allocateAtHasAllowAlternativeArg = false; + } + else if (parameters.Length == 4 && + parameters[0].ParameterType == typeof(ulong) && + parameters[1].ParameterType == typeof(ulong) && + parameters[2].ParameterType == typeof(bool) && + parameters[3].ParameterType == typeof(bool)) + { + allocateAt = candidate; + allocateAtHasAllowAlternativeArg = true; + } + } + + if (allocateAtOrAbove is not null && allocateAt is not null) { - allocateAt = candidate; - allocateAtHasAllowAlternativeArg = true; break; } } - if (allocateAt is not null) + if (allocateAtOrAbove is not null || allocateAt is not null) { break; } @@ -1721,6 +1748,18 @@ public static class KernelRuntimeCompatExports memoryObject = innerValue; } + if (allowSearch && allocateAtOrAbove is not null) + { + var searchArgs = new object[] { desiredAddress, length, false, alignment, 0UL }; + var searchResult = allocateAtOrAbove.Invoke(memoryObject, searchArgs); + if (searchResult is bool trueValue && trueValue && + searchArgs[4] is ulong searchedAddress && searchedAddress != 0) + { + mappedAddress = searchedAddress; + return true; + } + } + if (allocateAt is null) { Console.Error.WriteLine($"[LOADER][TRACE] reserve_virtual_range: AllocateAt missing on {ctx.Memory.GetType().FullName}"); @@ -1728,7 +1767,7 @@ public static class KernelRuntimeCompatExports } var invokeArgs = allocateAtHasAllowAlternativeArg - ? new object[] { desiredAddress, length, false, true } + ? new object[] { desiredAddress, length, false, false } : new object[] { desiredAddress, length, false }; var result = allocateAt.Invoke(memoryObject, invokeArgs); if (result is not ulong allocated || allocated == 0)