[fiber] synchronization problems have been fixed for such a titles: Demon's Souls

[ampr] new exports
[memory] trampoline fixes
This commit is contained in:
ParantezTech
2026-06-23 15:48:45 +03:00
parent dad85b87c3
commit d134f9b9f6
11 changed files with 1074 additions and 242 deletions
@@ -19,7 +19,12 @@ public sealed partial class DirectExecutionBackend
{ {
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_DISABLE_RAW_HANDLER"), "1", StringComparison.Ordinal)) 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}"); Console.Error.WriteLine($"[LOADER][INFO] Raw exception handler installed: 0x{_rawExceptionHandler:X16}");
} }
else else
@@ -29,12 +34,22 @@ public sealed partial class DirectExecutionBackend
_handlerDelegate = VectoredHandler; _handlerDelegate = VectoredHandler;
_handlerHandle = GCHandle.Alloc(_handlerDelegate); _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}"); Console.Error.WriteLine($"[LOADER][INFO] Exception handler installed: 0x{_exceptionHandler:X16}");
_unhandledFilterDelegate = UnhandledExceptionFilter; _unhandledFilterDelegate = UnhandledExceptionFilter;
_unhandledFilterHandle = GCHandle.Alloc(_unhandledFilterDelegate); _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) private unsafe int UnhandledExceptionFilter(void* exceptionInfo)
@@ -143,6 +143,8 @@ public sealed partial class DirectExecutionBackend
ProbeReturnRip(num7, num); ProbeReturnRip(num7, num);
} }
TrackStrlenPrelude(importStubEntry.Nid, num, num7); 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); bool logBootstrap = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_BOOTSTRAP"), "1", StringComparison.Ordinal);
if (logBootstrap && string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal)) if (logBootstrap && string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal))
{ {
@@ -279,13 +281,15 @@ public sealed partial class DirectExecutionBackend
catch catch
{ {
} }
TryBypassStackChkFailTrap(num, num7);
} }
try try
{ {
OrbisGen2Result orbisGen2Result; OrbisGen2Result orbisGen2Result;
bool dispatchResolved = true; bool dispatchResolved = true;
var previousImportCallFrame = GuestThreadExecution.EnterImportCallFrame(num7, (ulong)argPackPtr + 104uL); var previousImportCallFrame = GuestThreadExecution.EnterImportCallFrame(
num7,
(ulong)argPackPtr + 104uL,
ActiveGuestReturnSlotAddress);
try try
{ {
if (string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal)) 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) private unsafe bool TryForceGuestExitToHostStub(nint argPackPtr, long dispatchIndex, ulong returnRip, string nid)
{ {
ulong num = ActiveEntryReturnSentinelRip; ulong num = ActiveEntryReturnSentinelRip;
if (num < 65536) if (num < 65536 || !TryPatchActiveGuestReturnSlot(num))
{ {
return false; 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) private unsafe bool TryCompleteGuestEntryToHostStub(nint argPackPtr, long dispatchIndex, ulong returnRip, string nid, string reason, int status)
{ {
ulong hostExit = ActiveEntryReturnSentinelRip; ulong hostExit = ActiveEntryReturnSentinelRip;
if (hostExit < 65536) if (hostExit < 65536 || !TryPatchActiveGuestReturnSlot(hostExit))
{ {
return false; 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) private unsafe bool TryYieldGuestThreadToHostStub(nint argPackPtr, long dispatchIndex, ulong returnRip, string nid, string reason)
{ {
ulong hostExit = ActiveEntryReturnSentinelRip; ulong hostExit = ActiveEntryReturnSentinelRip;
if (hostExit < 65536) if (hostExit < 65536 || !TryPatchActiveGuestReturnSlot(hostExit))
{ {
return false; return false;
} }
@@ -470,6 +474,14 @@ public sealed partial class DirectExecutionBackend
return true; 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) private bool ShouldForceGuestExitOnImportLoop(string nid, ulong returnRip, long dispatchIndex, ulong arg0, ulong arg1)
{ {
if (dispatchIndex < 1200) if (dispatchIndex < 1200)
@@ -480,6 +492,11 @@ public sealed partial class DirectExecutionBackend
{ {
return false; return false;
} }
if (IsImportLoopGuardBoundary(nid))
{
ResetImportLoopPattern();
return false;
}
if (!_importNidHashCache.TryGetValue(nid, out var value)) if (!_importNidHashCache.TryGetValue(nid, out var value))
{ {
value = StableHash64(nid); value = StableHash64(nid);
@@ -513,6 +530,17 @@ public sealed partial class DirectExecutionBackend
return elapsedTicks >= (long)(guardSeconds * Stopwatch.Frequency); 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() private static int GetImportLoopGuardSeconds()
{ {
if (int.TryParse(Environment.GetEnvironmentVariable("SHARPEMU_IMPORT_LOOP_GUARD_SECONDS"), out var seconds)) 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) private unsafe void TryPatchEa020eLookupCall(long dispatchIndex, ulong returnRip)
{ {
if (_patchedEa020eLookupCall || returnRip != 0x0000000800EA01A6uL) if (_patchedEa020eLookupCall || returnRip != 0x0000000800EA01A6uL)
@@ -136,10 +136,18 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
private nint _unresolvedReturnStub; private nint _unresolvedReturnStub;
private nint _guestReturnStub;
private nint _rawExceptionHandler; private nint _rawExceptionHandler;
private nint _rawExceptionHandlerStub;
private nint _exceptionHandler; private nint _exceptionHandler;
private nint _exceptionHandlerStub;
private nint _unhandledFilterStub;
private nint _lowIndexedTableScratch; private nint _lowIndexedTableScratch;
private nint _stackGuardCompareScratch; private nint _stackGuardCompareScratch;
@@ -161,6 +169,9 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
[ThreadStatic] [ThreadStatic]
private static ulong _activeEntryReturnSentinelRip; private static ulong _activeEntryReturnSentinelRip;
[ThreadStatic]
private static ulong _activeGuestReturnSlotAddress;
[ThreadStatic] [ThreadStatic]
private static bool _activeForcedGuestExit; private static bool _activeForcedGuestExit;
@@ -202,8 +213,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
private readonly HashSet<ulong> _fallbackTrapStubs = new HashSet<ulong>(); private readonly HashSet<ulong> _fallbackTrapStubs = new HashSet<ulong>();
private readonly HashSet<ulong> _stackChkBypassSites = new HashSet<ulong>();
private readonly HashSet<ulong> _patchedResolverReturnSites = new HashSet<ulong>(); private readonly HashSet<ulong> _patchedResolverReturnSites = new HashSet<ulong>();
private readonly HashSet<ulong> _patchedTlsImmediateThunkTargets = new HashSet<ulong>(); private readonly HashSet<ulong> _patchedTlsImmediateThunkTargets = new HashSet<ulong>();
@@ -539,6 +548,9 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
} }
} }
private ulong ActiveGuestReturnSlotAddress =>
HasActiveExecutionThread ? _activeGuestReturnSlotAddress : 0;
private bool ActiveForcedGuestExit private bool ActiveForcedGuestExit
{ {
get => HasActiveExecutionThread ? _activeForcedGuestExit : _forcedGuestExit; get => HasActiveExecutionThread ? _activeForcedGuestExit : _forcedGuestExit;
@@ -591,6 +603,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
DirectExecutionBackend? previousBackend, DirectExecutionBackend? previousBackend,
CpuContext? previousContext, CpuContext? previousContext,
ulong previousSentinel, ulong previousSentinel,
ulong previousReturnSlotAddress,
bool previousForcedExit, bool previousForcedExit,
bool previousYieldRequested, bool previousYieldRequested,
string? previousYieldReason) string? previousYieldReason)
@@ -598,6 +611,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
_activeExecutionBackend = previousBackend; _activeExecutionBackend = previousBackend;
_activeCpuContext = previousContext; _activeCpuContext = previousContext;
_activeEntryReturnSentinelRip = previousSentinel; _activeEntryReturnSentinelRip = previousSentinel;
_activeGuestReturnSlotAddress = previousReturnSlotAddress;
_activeForcedGuestExit = previousForcedExit; _activeForcedGuestExit = previousForcedExit;
_activeGuestThreadYieldRequested = previousYieldRequested; _activeGuestThreadYieldRequested = previousYieldRequested;
_activeGuestThreadYieldReason = previousYieldReason; _activeGuestThreadYieldReason = previousYieldReason;
@@ -634,6 +648,11 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
throw new OutOfMemoryException("Failed to allocate host stack slot storage"); throw new OutOfMemoryException("Failed to allocate host stack slot storage");
} }
_unresolvedReturnStub = CreateUnresolvedReturnStub(); _unresolvedReturnStub = CreateUnresolvedReturnStub();
_guestReturnStub = CreateGuestReturnStub();
if (_guestReturnStub == 0)
{
throw new OutOfMemoryException("Failed to allocate guest return stub");
}
SetupExceptionHandler(); SetupExceptionHandler();
} }
@@ -930,9 +949,24 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
yield return "_" + exportName; 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) private unsafe void BindTlsBase(CpuContext context)
@@ -1243,6 +1277,145 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
return (nint)ptr; 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) private unsafe void* TryAllocateNearEntry(nuint size)
{ {
ulong entryPoint = _entryPoint; ulong entryPoint = _entryPoint;
@@ -1980,8 +2153,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
{ {
Rip = continuation.Rip, Rip = continuation.Rip,
Rflags = continuation.Rflags == 0 ? 0x202UL : continuation.Rflags, Rflags = continuation.Rflags == 0 ? 0x202UL : continuation.Rflags,
FsBase = continuation.FsBase != 0 ? continuation.FsBase : (callerContext.FsBase != 0 ? callerContext.FsBase : fallbackTlsBase), FsBase = callerContext.FsBase != 0 ? callerContext.FsBase : (continuation.FsBase != 0 ? continuation.FsBase : fallbackTlsBase),
GsBase = continuation.GsBase != 0 ? continuation.GsBase : (callerContext.GsBase != 0 ? callerContext.GsBase : fallbackTlsBase), GsBase = callerContext.GsBase != 0 ? callerContext.GsBase : (continuation.GsBase != 0 ? continuation.GsBase : fallbackTlsBase),
}; };
context[CpuRegister.Rax] = continuation.Rax; context[CpuRegister.Rax] = continuation.Rax;
@@ -1997,21 +2170,39 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
context[CpuRegister.R13] = continuation.R13; context[CpuRegister.R13] = continuation.R13;
context[CpuRegister.R14] = continuation.R14; context[CpuRegister.R14] = continuation.R14;
context[CpuRegister.R15] = continuation.R15; context[CpuRegister.R15] = continuation.R15;
context[CpuRegister.Rsp] = continuation.Rsp + 16uL; context[CpuRegister.Rsp] = continuation.Rsp;
var currentGuestThreadHandle = GuestThreadExecution.CurrentGuestThreadHandle;
var exitReason = GuestNativeCallExitReason.Exception; var exitReason = GuestNativeCallExitReason.Exception;
string? callbackReason = null; string? callbackReason = null;
string? callbackLastError = null; string? callbackLastError = null;
Exception? callbackException = null; Exception? callbackException = null;
var currentGuestThreadHandle = GuestThreadExecution.CurrentGuestThreadHandle;
var currentFiberAddress = GuestThreadExecution.CurrentFiberAddress;
void RunContinuation() 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; var previousLastError = LastError;
try 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; LastError = null;
exitReason = ExecuteGuestContinuationEntry(context, continuation.Rip, reason, out callbackReason); exitReason = ExecuteGuestContinuationEntry(
context,
continuation.Rip,
continuation.ReturnSlotAddress,
reason,
out callbackReason);
callbackLastError = LastError; callbackLastError = LastError;
} }
catch (Exception ex) catch (Exception ex)
@@ -2022,7 +2213,17 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
} }
finally finally
{ {
TraceGuestContext(
$"continuation-exit reason={reason} managed={Environment.CurrentManagedThreadId} guest=0x{GuestThreadExecution.CurrentGuestThreadHandle:X16} fiber=0x{GuestThreadExecution.CurrentFiberAddress:X16} exit={exitReason}");
LastError = previousLastError; 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); 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 else
{ {
RunContinuationOnTemporaryThread(currentGuestThreadHandle, RunContinuation); RunContinuationOnTemporaryThread(currentGuestThreadHandle, RunContinuation);
@@ -2070,6 +2277,14 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
return true; 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) private static void RunContinuationOnTemporaryThread(ulong guestThreadHandle, Action continuation)
{ {
var continuationThread = new Thread(() => var continuationThread = new Thread(() =>
@@ -2351,6 +2566,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
var previousActiveBackend = _activeExecutionBackend; var previousActiveBackend = _activeExecutionBackend;
var previousActiveContext = _activeCpuContext; var previousActiveContext = _activeCpuContext;
var previousSentinel = _activeEntryReturnSentinelRip; var previousSentinel = _activeEntryReturnSentinelRip;
var previousReturnSlotAddress = _activeGuestReturnSlotAddress;
var previousForcedExit = _activeForcedGuestExit; var previousForcedExit = _activeForcedGuestExit;
var previousYieldRequested = _activeGuestThreadYieldRequested; var previousYieldRequested = _activeGuestThreadYieldRequested;
var previousYieldReason = _activeGuestThreadYieldReason; var previousYieldReason = _activeGuestThreadYieldReason;
@@ -2360,6 +2576,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
_activeExecutionBackend = this; _activeExecutionBackend = this;
_activeCpuContext = context; _activeCpuContext = context;
_activeEntryReturnSentinelRip = 0; _activeEntryReturnSentinelRip = 0;
_activeGuestReturnSlotAddress = 0;
_activeForcedGuestExit = false; _activeForcedGuestExit = false;
_activeGuestThreadYieldRequested = false; _activeGuestThreadYieldRequested = false;
_activeGuestThreadYieldReason = null; _activeGuestThreadYieldReason = null;
@@ -2463,7 +2680,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
ptr2[offset++] = 91; ptr2[offset++] = 91;
ptr2[offset++] = 195; ptr2[offset++] = 195;
ulong sentinel = (ulong)ptr + (ulong)sentinelOffset; ulong sentinel = (ulong)ptr + (ulong)sentinelOffset;
ActiveEntryReturnSentinelRip = sentinel; ActiveEntryReturnSentinelRip = (ulong)_guestReturnStub;
_activeGuestReturnSlotAddress = context[CpuRegister.Rsp] - 16uL;
if (!context.TryWriteUInt64(context[CpuRegister.Rsp], sentinel)) if (!context.TryWriteUInt64(context[CpuRegister.Rsp], sentinel))
{ {
reason = $"failed to patch guest thread return sentinel at 0x{context[CpuRegister.Rsp]:X16}"; 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, previousActiveBackend,
previousActiveContext, previousActiveContext,
previousSentinel, previousSentinel,
previousReturnSlotAddress,
previousForcedExit, previousForcedExit,
previousYieldRequested, previousYieldRequested,
previousYieldReason); 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; reason = null;
if (context[CpuRegister.Rsp] == 0) if (context[CpuRegister.Rsp] == 0)
@@ -2534,6 +2758,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
var previousActiveBackend = _activeExecutionBackend; var previousActiveBackend = _activeExecutionBackend;
var previousActiveContext = _activeCpuContext; var previousActiveContext = _activeCpuContext;
var previousSentinel = _activeEntryReturnSentinelRip; var previousSentinel = _activeEntryReturnSentinelRip;
var previousReturnSlotAddress = _activeGuestReturnSlotAddress;
var previousForcedExit = _activeForcedGuestExit; var previousForcedExit = _activeForcedGuestExit;
var previousYieldRequested = _activeGuestThreadYieldRequested; var previousYieldRequested = _activeGuestThreadYieldRequested;
var previousYieldReason = _activeGuestThreadYieldReason; var previousYieldReason = _activeGuestThreadYieldReason;
@@ -2543,6 +2768,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
_activeExecutionBackend = this; _activeExecutionBackend = this;
_activeCpuContext = context; _activeCpuContext = context;
_activeEntryReturnSentinelRip = 0; _activeEntryReturnSentinelRip = 0;
_activeGuestReturnSlotAddress = returnSlotAddress;
_activeForcedGuestExit = false; _activeForcedGuestExit = false;
_activeGuestThreadYieldRequested = false; _activeGuestThreadYieldRequested = false;
_activeGuestThreadYieldReason = null; _activeGuestThreadYieldReason = null;
@@ -2577,7 +2803,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
Emit(0x49); Emit(0x89); Emit(0x22); // mov [r10], rsp Emit(0x49); Emit(0x89); Emit(0x22); // mov [r10], rsp
EmitMovR64Imm(0x48, 0xB8, context[CpuRegister.Rsp]); // mov rax, guest rsp EmitMovR64Imm(0x48, 0xB8, context[CpuRegister.Rsp]); // mov rax, guest rsp
Emit(0x48); Emit(0x89); Emit(0xC4); // mov rsp, rax 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, 0xBB, context[CpuRegister.Rbx]); // mov rbx, imm64
EmitMovR64Imm(0x48, 0xBD, context[CpuRegister.Rbp]); // mov rbp, imm64 EmitMovR64Imm(0x48, 0xBD, context[CpuRegister.Rbp]); // mov rbp, imm64
EmitMovR64Imm(0x48, 0xBF, context[CpuRegister.Rdi]); // mov rdi, 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(0x49, 0xBF, context[CpuRegister.R15]); // mov r15, imm64
EmitMovR64Imm(0x48, 0xB8, context[CpuRegister.Rax]); // mov rax, imm64 EmitMovR64Imm(0x48, 0xB8, context[CpuRegister.Rax]); // mov rax, imm64
EmitMovR64Imm(0x49, 0xBB, entryPoint); // mov r11, entryPoint EmitMovR64Imm(0x49, 0xBB, entryPoint); // mov r11, entryPoint
Emit(0x41); Emit(0xFF); Emit(0xD3); // call r11 Emit(0x41); Emit(0xFF); Emit(0xE3); // jmp r11
int sentinelOffset = offset + 4; ActiveEntryReturnSentinelRip = (ulong)_guestReturnStub;
Emit(0x48); Emit(0x83); Emit(0xC4); Emit(0x08); // add rsp, 8 if (returnSlotAddress == 0 || !context.TryWriteUInt64(returnSlotAddress, (ulong)_guestReturnStub))
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))
{ {
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; return GuestNativeCallExitReason.Exception;
} }
uint oldProtect = default(uint); uint oldProtect = default(uint);
@@ -2657,6 +2864,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
previousActiveBackend, previousActiveBackend,
previousActiveContext, previousActiveContext,
previousSentinel, previousSentinel,
previousReturnSlotAddress,
previousForcedExit, previousForcedExit,
previousYieldRequested, previousYieldRequested,
previousYieldReason); previousYieldReason);
@@ -2717,9 +2925,18 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
} }
EmitByte(code, ref offset, 0x0F); EmitByte(code, ref offset, 0x0F);
EmitByte(code, ref offset, store ? (byte)0x7F : (byte)0x6F); EmitByte(code, ref offset, store ? (byte)0x7F : (byte)0x6F);
EmitByte(code, ref offset, (byte)(0x44 | ((xmm & 7) << 3))); if (displacement < 0x80)
EmitByte(code, ref offset, 0x24); {
EmitByte(code, ref offset, displacement); 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) 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 previousActiveBackend = _activeExecutionBackend;
var previousActiveContext = _activeCpuContext; var previousActiveContext = _activeCpuContext;
var previousSentinel = _activeEntryReturnSentinelRip; var previousSentinel = _activeEntryReturnSentinelRip;
var previousReturnSlotAddress = _activeGuestReturnSlotAddress;
var previousForcedExit = _activeForcedGuestExit; var previousForcedExit = _activeForcedGuestExit;
var previousYieldRequested = _activeGuestThreadYieldRequested; var previousYieldRequested = _activeGuestThreadYieldRequested;
var previousYieldReason = _activeGuestThreadYieldReason; var previousYieldReason = _activeGuestThreadYieldReason;
@@ -2754,6 +2972,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
_activeExecutionBackend = this; _activeExecutionBackend = this;
_activeCpuContext = context; _activeCpuContext = context;
_activeEntryReturnSentinelRip = 0; _activeEntryReturnSentinelRip = 0;
_activeGuestReturnSlotAddress = 0;
_activeForcedGuestExit = false; _activeForcedGuestExit = false;
_activeGuestThreadYieldRequested = false; _activeGuestThreadYieldRequested = false;
_activeGuestThreadYieldReason = null; _activeGuestThreadYieldReason = null;
@@ -2857,7 +3076,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
ptr2[num3++] = 91; ptr2[num3++] = 91;
ptr2[num3++] = 195; ptr2[num3++] = 195;
ulong value = (ulong)ptr + (ulong)num4; ulong value = (ulong)ptr + (ulong)num4;
ActiveEntryReturnSentinelRip = value; ActiveEntryReturnSentinelRip = (ulong)_guestReturnStub;
_activeGuestReturnSlotAddress = context[CpuRegister.Rsp] - 16uL;
if (!context.TryWriteUInt64(context[CpuRegister.Rsp], value)) if (!context.TryWriteUInt64(context[CpuRegister.Rsp], value))
{ {
LastError = $"Failed to patch native return sentinel at 0x{context[CpuRegister.Rsp]:X16}"; 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, previousActiveBackend,
previousActiveContext, previousActiveContext,
previousSentinel, previousSentinel,
previousReturnSlotAddress,
previousForcedExit, previousForcedExit,
previousYieldRequested, previousYieldRequested,
previousYieldReason); previousYieldReason);
@@ -3116,6 +3337,22 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
RemoveVectoredExceptionHandler((void*)_rawExceptionHandler); RemoveVectoredExceptionHandler((void*)_rawExceptionHandler);
_rawExceptionHandler = 0; _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) if (_handlerHandle.IsAllocated)
{ {
_handlerHandle.Free(); _handlerHandle.Free();
@@ -3172,6 +3409,11 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
VirtualFree((void*)_unresolvedReturnStub, 0u, 32768u); VirtualFree((void*)_unresolvedReturnStub, 0u, 32768u);
_unresolvedReturnStub = 0; _unresolvedReturnStub = 0;
} }
if (_guestReturnStub != 0)
{
VirtualFree((void*)_guestReturnStub, 0u, 32768u);
_guestReturnStub = 0;
}
if (_lowIndexedTableScratch != 0) if (_lowIndexedTableScratch != 0)
{ {
VirtualFree((void*)_lowIndexedTableScratch, 0u, 32768u); VirtualFree((void*)_lowIndexedTableScratch, 0u, 32768u);
@@ -222,6 +222,56 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
return actualAddress; 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) public bool TryAllocateGuestMemory(ulong size, ulong alignment, out ulong address)
{ {
address = 0; address = 0;
@@ -532,6 +582,30 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
return null; 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) private static bool TryResolveRegionOffset(ulong address, ulong size, MemoryRegion region, out ulong offset)
{ {
offset = 0; offset = 0;
+38 -4
View File
@@ -36,11 +36,13 @@ public interface IGuestThreadScheduler
public readonly record struct GuestImportCallFrame( public readonly record struct GuestImportCallFrame(
bool IsValid, bool IsValid,
ulong ReturnRip, ulong ReturnRip,
ulong ResumeRsp); ulong ResumeRsp,
ulong ReturnSlotAddress);
public readonly record struct GuestCpuContinuation( public readonly record struct GuestCpuContinuation(
ulong Rip, ulong Rip,
ulong Rsp, ulong Rsp,
ulong ReturnSlotAddress,
ulong Rflags, ulong Rflags,
ulong FsBase, ulong FsBase,
ulong GsBase, ulong GsBase,
@@ -63,6 +65,9 @@ public static class GuestThreadExecution
[ThreadStatic] [ThreadStatic]
private static ulong _currentGuestThreadHandle; private static ulong _currentGuestThreadHandle;
[ThreadStatic]
private static ulong _currentFiberAddress;
[ThreadStatic] [ThreadStatic]
private static string? _pendingBlockReason; private static string? _pendingBlockReason;
@@ -84,12 +89,17 @@ public static class GuestThreadExecution
[ThreadStatic] [ThreadStatic]
private static ulong _currentImportResumeRsp; private static ulong _currentImportResumeRsp;
[ThreadStatic]
private static ulong _currentImportReturnSlotAddress;
public static IGuestThreadScheduler? Scheduler { get; set; } public static IGuestThreadScheduler? Scheduler { get; set; }
public static bool IsGuestThread => _currentGuestThreadHandle != 0; public static bool IsGuestThread => _currentGuestThreadHandle != 0;
public static ulong CurrentGuestThreadHandle => _currentGuestThreadHandle; public static ulong CurrentGuestThreadHandle => _currentGuestThreadHandle;
public static ulong CurrentFiberAddress => _currentFiberAddress;
public static ulong EnterGuestThread(ulong threadHandle) public static ulong EnterGuestThread(ulong threadHandle)
{ {
var previous = _currentGuestThreadHandle; var previous = _currentGuestThreadHandle;
@@ -101,6 +111,7 @@ public static class GuestThreadExecution
_hasCurrentImportCallFrame = false; _hasCurrentImportCallFrame = false;
_currentImportReturnRip = 0; _currentImportReturnRip = 0;
_currentImportResumeRsp = 0; _currentImportResumeRsp = 0;
_currentImportReturnSlotAddress = 0;
return previous; return previous;
} }
@@ -114,6 +125,19 @@ public static class GuestThreadExecution
_hasCurrentImportCallFrame = false; _hasCurrentImportCallFrame = false;
_currentImportReturnRip = 0; _currentImportReturnRip = 0;
_currentImportResumeRsp = 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) public static bool RequestCurrentThreadBlock(string reason)
@@ -161,15 +185,20 @@ public static class GuestThreadExecution
return true; return true;
} }
public static GuestImportCallFrame EnterImportCallFrame(ulong returnRip, ulong resumeRsp) public static GuestImportCallFrame EnterImportCallFrame(
ulong returnRip,
ulong resumeRsp,
ulong returnSlotAddress)
{ {
var previous = new GuestImportCallFrame( var previous = new GuestImportCallFrame(
_hasCurrentImportCallFrame, _hasCurrentImportCallFrame,
_currentImportReturnRip, _currentImportReturnRip,
_currentImportResumeRsp); _currentImportResumeRsp,
_currentImportReturnSlotAddress);
_hasCurrentImportCallFrame = true; _hasCurrentImportCallFrame = true;
_currentImportReturnRip = returnRip; _currentImportReturnRip = returnRip;
_currentImportResumeRsp = resumeRsp; _currentImportResumeRsp = resumeRsp;
_currentImportReturnSlotAddress = returnSlotAddress;
return previous; return previous;
} }
@@ -178,6 +207,7 @@ public static class GuestThreadExecution
_hasCurrentImportCallFrame = previous.IsValid; _hasCurrentImportCallFrame = previous.IsValid;
_currentImportReturnRip = previous.ReturnRip; _currentImportReturnRip = previous.ReturnRip;
_currentImportResumeRsp = previous.ResumeRsp; _currentImportResumeRsp = previous.ResumeRsp;
_currentImportReturnSlotAddress = previous.ReturnSlotAddress;
} }
public static bool TryGetCurrentImportCallFrame(out GuestImportCallFrame frame) public static bool TryGetCurrentImportCallFrame(out GuestImportCallFrame frame)
@@ -188,7 +218,11 @@ public static class GuestThreadExecution
return false; return false;
} }
frame = new GuestImportCallFrame(true, _currentImportReturnRip, _currentImportResumeRsp); frame = new GuestImportCallFrame(
true,
_currentImportReturnRip,
_currentImportResumeRsp,
_currentImportReturnSlotAddress);
return true; return true;
} }
} }
+172
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE; using SharpEmu.HLE;
using SharpEmu.Libs.Kernel;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@@ -17,8 +18,10 @@ public static class AmprExports
private const ulong CommandBufferAux1Offset = 0x20; private const ulong CommandBufferAux1Offset = 0x20;
private const ulong ReadFileRecordSize = 0x30; private const ulong ReadFileRecordSize = 0x30;
private const ulong KernelEventQueueRecordSize = 0x30; private const ulong KernelEventQueueRecordSize = 0x30;
private const ulong WriteAddressRecordSize = 0x20;
private const uint ReadFileRecordType = 1; private const uint ReadFileRecordType = 1;
private const uint KernelEventQueueRecordType = 2; private const uint KernelEventQueueRecordType = 2;
private const uint WriteAddressRecordType = 3;
private static readonly ConcurrentDictionary<ulong, CommandBufferState> _commandBuffers = new(); private static readonly ConcurrentDictionary<ulong, CommandBufferState> _commandBuffers = new();
private sealed class CommandBufferState private sealed class CommandBufferState
@@ -284,6 +287,18 @@ public static class AmprExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; 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( [SysAbiExport(
Nid = "tZDDEo2tE5k", Nid = "tZDDEo2tE5k",
ExportName = "sceAmprCommandBufferGetSize", ExportName = "sceAmprCommandBufferGetSize",
@@ -362,6 +377,92 @@ public static class AmprExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; 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( private static bool InitializeCommandBuffer(
CpuContext ctx, CpuContext ctx,
ulong commandBuffer, ulong commandBuffer,
@@ -576,6 +677,17 @@ public static class AmprExports
return AppendCommandBufferRecord(ctx, commandBuffer, record); return AppendCommandBufferRecord(ctx, commandBuffer, record);
} }
private static bool AppendWriteAddressRecord(CpuContext ctx, ulong commandBuffer, ulong address, ulong value)
{
Span<byte> 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<byte> record) private static bool AppendCommandBufferRecord(CpuContext ctx, ulong commandBuffer, ReadOnlySpan<byte> record)
{ {
if (!TryGetCommandBufferState(ctx, commandBuffer, out _, out _, out var state) || state is null) if (!TryGetCommandBufferState(ctx, commandBuffer, out _, out _, out var state) || state is null)
@@ -604,6 +716,66 @@ public static class AmprExports
return true; return true;
} }
private static bool CompleteKernelEventQueueRecord(CpuContext ctx, ulong recordAddress)
{
Span<byte> 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<byte> 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<byte> 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) 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)) if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AMPR"), "1", StringComparison.Ordinal))
+111 -32
View File
@@ -230,9 +230,9 @@ public static class FiberExports
LibraryName = "libSceFiber")] LibraryName = "libSceFiber")]
public static int FiberReturnToThread(CpuContext ctx) public static int FiberReturnToThread(CpuContext ctx)
{ {
var fiberAddress = _currentFiberAddress; var fiberAddress = ResolveCurrentFiberAddress(ctx);
var inferredFiber = false; var inferredFiber = false;
if (fiberAddress == 0 && TryFindFiberByStack(ctx, out fiberAddress)) if (_currentFiberAddress == 0 && fiberAddress != 0)
{ {
inferredFiber = true; inferredFiber = true;
} }
@@ -258,9 +258,9 @@ public static class FiberExports
if (GuestThreadExecution.TryGetCurrentImportCallFrame(out var frame)) if (GuestThreadExecution.TryGetCurrentImportCallFrame(out var frame))
{ {
_continuations[fiberAddress] = new FiberContinuation( _continuations[fiberAddress] = new FiberContinuation(
CaptureContinuation(ctx, frame.ReturnRip, frame.ResumeRsp), CaptureContinuation(ctx, frame.ReturnRip, frame.ResumeRsp, frame.ReturnSlotAddress),
argOnRunAddress); 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 else
{ {
@@ -284,12 +284,13 @@ public static class FiberExports
return SetReturn(ctx, FiberErrorNull); return SetReturn(ctx, FiberErrorNull);
} }
if (_currentFiberAddress == 0) var fiberAddress = ResolveCurrentFiberAddress(ctx);
if (fiberAddress == 0)
{ {
return SetReturn(ctx, FiberErrorPermission); return SetReturn(ctx, FiberErrorPermission);
} }
return TryWriteUInt64(ctx, outAddress, _currentFiberAddress) return TryWriteUInt64(ctx, outAddress, fiberAddress)
? SetReturn(ctx, 0) ? SetReturn(ctx, 0)
: SetReturn(ctx, FiberErrorInvalid); : SetReturn(ctx, FiberErrorInvalid);
} }
@@ -407,7 +408,7 @@ public static class FiberExports
return SetReturn(ctx, FiberErrorNull); return SetReturn(ctx, FiberErrorNull);
} }
if (_currentFiberAddress == 0) if (ResolveCurrentFiberAddress(ctx) == 0)
{ {
return SetReturn(ctx, FiberErrorPermission); return SetReturn(ctx, FiberErrorPermission);
} }
@@ -537,10 +538,11 @@ public static class FiberExports
if (fields.State != FiberStateIdle) if (fields.State != FiberStateIdle)
{ {
TraceFiber($"run-state-error reason={reason} fiber=0x{fiber:X16} state=0x{fields.State:X8}");
return SetReturn(ctx, FiberErrorState); return SetReturn(ctx, FiberErrorState);
} }
var previousFiber = _currentFiberAddress; var previousFiber = ResolveCurrentFiberAddress(ctx);
var switchingFromFiber = isSwitch && previousFiber != 0 && previousFiber != fiber; var switchingFromFiber = isSwitch && previousFiber != 0 && previousFiber != fiber;
if (isSwitch && previousFiber == 0) if (isSwitch && previousFiber == 0)
{ {
@@ -553,56 +555,111 @@ public static class FiberExports
return SetReturn(ctx, FiberErrorPermission); 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 (!TryWriteUInt32(ctx, fiber + FiberStateOffset, FiberStateRun))
{ {
if (ownsSession)
{
_runSessions.TryRemove(fiber, out _);
}
return SetReturn(ctx, FiberErrorInvalid); return SetReturn(ctx, FiberErrorInvalid);
} }
if (switchingFromFiber && !TryWriteUInt32(ctx, previousFiber + FiberStateOffset, FiberStateIdle)) if (switchingFromFiber && !TryWriteUInt32(ctx, previousFiber + FiberStateOffset, FiberStateIdle))
{ {
_ = TryWriteUInt32(ctx, fiber + FiberStateOffset, FiberStateIdle); _ = TryWriteUInt32(ctx, fiber + FiberStateOffset, FiberStateIdle);
if (ownsSession)
{
_runSessions.TryRemove(fiber, out _);
}
return SetReturn(ctx, FiberErrorInvalid); 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 previousReturnRequested = _fiberReturnRequested;
var previousReturnArgument = _fiberReturnArgument; var previousReturnArgument = _fiberReturnArgument;
var session = new FiberRunSession();
_runSessions[fiber] = session;
_currentFiberAddress = fiber; _currentFiberAddress = fiber;
_fiberReturnRequested = false; _fiberReturnRequested = false;
_fiberReturnArgument = 0; _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); var hasContinuation = _continuations.TryGetValue(fiber, out var continuation);
bool callbackOk;
string? callbackError;
if (hasContinuation) if (hasContinuation)
{ {
if (continuation.ArgOnRunAddress != 0 && _continuations.TryRemove(fiber, out _);
!TryWriteUInt64(ctx, continuation.ArgOnRunAddress, argOnRun)) }
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; if (continuation.ArgOnRunAddress != 0 &&
callbackError = $"failed to write resumed argOnRun to 0x{continuation.ArgOnRunAddress:X16}"; !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 else
{ {
callbackOk = scheduler.TryCallGuestContinuation( callbackOk = scheduler.TryCallGuestFunction(
ctx, ctx,
continuation.Context, fields.Entry,
fields.ArgOnInitialize,
argOnRun,
fields.ContextAddress,
fields.ContextSize,
reason, reason,
out callbackError); out callbackError);
} }
} }
else finally
{ {
callbackOk = scheduler.TryCallGuestFunction( GuestThreadExecution.RestoreFiber(previousExecutionFiber);
ctx,
fields.Entry,
fields.ArgOnInitialize,
argOnRun,
fields.ContextAddress,
fields.ContextSize,
reason,
out callbackError);
} }
var returnRequested = _fiberReturnRequested; var returnRequested = _fiberReturnRequested;
@@ -613,11 +670,10 @@ public static class FiberExports
returnArgument = sessionReturnArgument; returnArgument = sessionReturnArgument;
} }
if (!returnRequested) if (ownsSession)
{ {
_continuations.TryRemove(fiber, out _); _runSessions.TryRemove(fiber, out _);
} }
_runSessions.TryRemove(fiber, out _);
_currentFiberAddress = previousFiber; _currentFiberAddress = previousFiber;
_fiberReturnRequested = previousReturnRequested; _fiberReturnRequested = previousReturnRequested;
@@ -644,10 +700,15 @@ public static class FiberExports
return SetReturn(ctx, 0); 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( new(
resumeRip, resumeRip,
resumeRsp, resumeRsp,
returnSlotAddress,
ctx.Rflags == 0 ? 0x202UL : ctx.Rflags, ctx.Rflags == 0 ? 0x202UL : ctx.Rflags,
ctx.FsBase, ctx.FsBase,
ctx.GsBase, ctx.GsBase,
@@ -665,6 +726,24 @@ public static class FiberExports
ctx[CpuRegister.R14], ctx[CpuRegister.R14],
ctx[CpuRegister.R15]); 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( private static int AttachContext(
CpuContext ctx, CpuContext ctx,
ulong fiber, ulong fiber,
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE; using SharpEmu.HLE;
using SharpEmu.Libs.Ampr;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading; using System.Threading;
@@ -38,6 +39,12 @@ public static class KernelAprCompatExports
_submittedCommandBuffers[submissionId] = commandBuffer; _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)) if (outSubmissionId != 0 && !TryWriteUInt32(ctx, outSubmissionId, submissionId))
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
@@ -93,6 +100,12 @@ public static class KernelAprCompatExports
var submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId)); var submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId));
_submittedCommandBuffers[submissionId] = commandBuffer; _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); TraceApr(ctx, "submit", submissionId, commandBuffer, ctx[CpuRegister.Rsi], 0);
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -114,6 +127,12 @@ public static class KernelAprCompatExports
var submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId)); var submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId));
_submittedCommandBuffers[submissionId] = commandBuffer; _submittedCommandBuffers[submissionId] = commandBuffer;
var completionResult = AmprExports.CompleteCommandBuffer(ctx, commandBuffer);
if (completionResult != (int)OrbisGen2Result.ORBIS_GEN2_OK)
{
return completionResult;
}
if (!TryWriteUInt32(ctx, outSubmissionId, submissionId)) if (!TryWriteUInt32(ctx, outSubmissionId, submissionId))
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
@@ -154,5 +173,12 @@ public static class KernelAprCompatExports
_ = ctx.TryReadUInt64(ctx[CpuRegister.Rsp], out returnRip); _ = ctx.TryReadUInt64(ctx[CpuRegister.Rsp], out returnRip);
Console.Error.WriteLine( 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}"); $"[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}");
}
} }
} }
@@ -5,6 +5,7 @@ using System.Buffers.Binary;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Text; using System.Text;
using SharpEmu.HLE; using SharpEmu.HLE;
using SharpEmu.Libs.Fiber;
namespace SharpEmu.Libs.Kernel; namespace SharpEmu.Libs.Kernel;
@@ -113,6 +114,7 @@ public static class KernelEventFlagCompatExports
{ {
var handle = ctx[CpuRegister.Rdi]; var handle = ctx[CpuRegister.Rdi];
var pattern = ctx[CpuRegister.Rsi]; var pattern = ctx[CpuRegister.Rsi];
var returnRip = GetCurrentReturnRip();
if (!_eventFlags.TryGetValue(handle, out var state)) if (!_eventFlags.TryGetValue(handle, out var state))
{ {
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND); return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
@@ -122,7 +124,7 @@ public static class KernelEventFlagCompatExports
{ {
state.Bits |= pattern; state.Bits |= pattern;
Monitor.PulseAll(state.Gate); 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); return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
@@ -203,6 +205,7 @@ public static class KernelEventFlagCompatExports
var waitMode = unchecked((uint)ctx[CpuRegister.Rdx]); var waitMode = unchecked((uint)ctx[CpuRegister.Rdx]);
var resultAddress = ctx[CpuRegister.Rcx]; var resultAddress = ctx[CpuRegister.Rcx];
var timeoutAddress = ctx[CpuRegister.R8]; var timeoutAddress = ctx[CpuRegister.R8];
var returnRip = GetCurrentReturnRip();
if (!_eventFlags.TryGetValue(handle, out var state)) if (!_eventFlags.TryGetValue(handle, out var state))
{ {
@@ -232,11 +235,17 @@ public static class KernelEventFlagCompatExports
{ {
_ = TryWriteUInt32(ctx, timeoutAddress, 0); _ = TryWriteUInt32(ctx, timeoutAddress, 0);
_ = TryWriteResultPattern(ctx, resultAddress, state.Bits); _ = 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); 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; var scheduler = GuestThreadExecution.Scheduler;
if (scheduler is null) if (scheduler is null)
@@ -245,7 +254,7 @@ public static class KernelEventFlagCompatExports
} }
state.WaitingThreads++; 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; var releaseWaiter = true;
try try
{ {
@@ -265,7 +274,7 @@ public static class KernelEventFlagCompatExports
{ {
state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1); state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1);
releaseWaiter = false; 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); return SetReturn(ctx, pumpedWaitResult);
} }
@@ -282,7 +291,7 @@ public static class KernelEventFlagCompatExports
} }
state.WaitingThreads++; 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); return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
} }
finally finally
@@ -398,6 +407,32 @@ public static class KernelEventFlagCompatExports
return true; return true;
} }
private static bool TryReadUInt64(CpuContext ctx, ulong address, out ulong value)
{
Span<byte> 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<byte> 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) private static bool TryWriteUInt32(CpuContext ctx, ulong address, uint value)
{ {
Span<byte> buffer = stackalloc byte[sizeof(uint)]; Span<byte> buffer = stackalloc byte[sizeof(uint)];
@@ -444,4 +479,101 @@ public static class KernelEventFlagCompatExports
Console.Error.WriteLine($"[LOADER][TRACE] event_flag.{message}"); 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<ulong> 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}");
}
}
} }
@@ -3,6 +3,7 @@
using SharpEmu.HLE; using SharpEmu.HLE;
using SharpEmu.Libs.Ampr; using SharpEmu.Libs.Ampr;
using System.Buffers;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@@ -164,7 +165,7 @@ public static class KernelMemoryCompatExports
var desiredAddress = AlignUp( var desiredAddress = AlignUp(
_nextVirtualAddress == 0 ? 0x1_0000_0000UL : _nextVirtualAddress, _nextVirtualAddress == 0 ? 0x1_0000_0000UL : _nextVirtualAddress,
effectiveAlignment); effectiveAlignment);
if (!TryReserveGuestVirtualRange(ctx, desiredAddress, mappedLength, OrbisProtCpuReadWrite, out address) || if (!TryReserveGuestVirtualRange(ctx, desiredAddress, mappedLength, OrbisProtCpuReadWrite, effectiveAlignment, out address) ||
address == 0) address == 0)
{ {
return false; return false;
@@ -597,7 +598,6 @@ public static class KernelMemoryCompatExports
ulong NextGpArg() => vaCursor.NextGpArg(); ulong NextGpArg() => vaCursor.NextGpArg();
double NextFloatArg() => vaCursor.NextFloatArg(); double NextFloatArg() => vaCursor.NextFloatArg();
rendered = FormatString(ctx, format, NextGpArg, NextFloatArg); rendered = FormatString(ctx, format, NextGpArg, NextFloatArg);
vaCursor.Commit();
} }
Console.Write(rendered); Console.Write(rendered);
@@ -2242,7 +2242,7 @@ public static class KernelMemoryCompatExports
} }
else else
{ {
reserved = TryReserveGuestVirtualRange(ctx, desiredAddress, length, protection, out mappedAddress); reserved = TryReserveGuestVirtualRange(ctx, desiredAddress, length, protection, effectiveAlignment, out mappedAddress);
} }
if (ShouldTraceDirectMemory()) if (ShouldTraceDirectMemory())
{ {
@@ -2331,7 +2331,7 @@ public static class KernelMemoryCompatExports
{ {
mappedAddress = requestedAddress; 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 mappedAddress = requestedAddress != 0 && fixedMapping
? requestedAddress ? requestedAddress
@@ -2721,7 +2721,6 @@ public static class KernelMemoryCompatExports
ulong NextGpArg() => vaCursor.NextGpArg(); ulong NextGpArg() => vaCursor.NextGpArg();
double NextFloatArg() => vaCursor.NextFloatArg(); double NextFloatArg() => vaCursor.NextFloatArg();
var rendered = FormatString(ctx, format, NextGpArg, NextFloatArg); var rendered = FormatString(ctx, format, NextGpArg, NextFloatArg);
vaCursor.Commit();
var outputBytes = Encoding.UTF8.GetBytes(rendered); var outputBytes = Encoding.UTF8.GetBytes(rendered);
return WriteSnprintfOutput(ctx, destination, bufferSize, outputBytes); return WriteSnprintfOutput(ctx, destination, bufferSize, outputBytes);
@@ -2768,7 +2767,6 @@ public static class KernelMemoryCompatExports
ulong NextGpArg() => vaCursor.NextGpArg(); ulong NextGpArg() => vaCursor.NextGpArg();
double NextFloatArg() => vaCursor.NextFloatArg(); double NextFloatArg() => vaCursor.NextFloatArg();
rendered = FormatString(ctx, format, NextGpArg, NextFloatArg); rendered = FormatString(ctx, format, NextGpArg, NextFloatArg);
vaCursor.Commit();
} }
TraceWidePrintf(ctx, "vswprintf", destination, bufferSize, format, rendered); TraceWidePrintf(ctx, "vswprintf", destination, bufferSize, format, rendered);
@@ -3552,13 +3550,6 @@ public static class KernelMemoryCompatExports
: 0.0; : 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 GpOffset => _gpOffset;
public uint FpOffset => _fpOffset; public uint FpOffset => _fpOffset;
@@ -3613,6 +3604,7 @@ public static class KernelMemoryCompatExports
ulong desiredAddress, ulong desiredAddress,
ulong length, ulong length,
int protection, int protection,
ulong alignment,
out ulong mappedAddress) out ulong mappedAddress)
{ {
mappedAddress = 0; mappedAddress = 0;
@@ -3625,40 +3617,51 @@ public static class KernelMemoryCompatExports
{ {
object memoryObject = ctx.Memory; object memoryObject = ctx.Memory;
MethodInfo? allocateAt = null; MethodInfo? allocateAt = null;
MethodInfo? allocateAtOrAbove = null;
var allocateAtHasAllowAlternativeArg = false; var allocateAtHasAllowAlternativeArg = false;
for (var depth = 0; depth < 4; depth++) for (var depth = 0; depth < 4; depth++)
{ {
foreach (var candidate in memoryObject.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)) foreach (var candidate in memoryObject.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance))
{ {
if (!string.Equals(candidate.Name, "AllocateAt", StringComparison.Ordinal))
{
continue;
}
var parameters = candidate.GetParameters(); var parameters = candidate.GetParameters();
if (parameters.Length == 3 && if (string.Equals(candidate.Name, "TryAllocateAtOrAbove", StringComparison.Ordinal) &&
parameters[0].ParameterType == typeof(ulong) && parameters.Length == 5 &&
parameters[1].ParameterType == typeof(ulong) &&
parameters[2].ParameterType == typeof(bool))
{
allocateAt = candidate;
allocateAtHasAllowAlternativeArg = false;
break;
}
if (parameters.Length == 4 &&
parameters[0].ParameterType == typeof(ulong) && parameters[0].ParameterType == typeof(ulong) &&
parameters[1].ParameterType == typeof(ulong) && parameters[1].ParameterType == typeof(ulong) &&
parameters[2].ParameterType == typeof(bool) && 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; break;
} }
} }
if (allocateAt is not null) if (allocateAtOrAbove is not null || allocateAt is not null)
{ {
break; break;
} }
@@ -3678,15 +3681,27 @@ public static class KernelMemoryCompatExports
memoryObject = innerValue; 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) if (allocateAt is null)
{ {
Console.Error.WriteLine($"[LOADER][TRACE] reserve range: AllocateAt missing on {ctx.Memory.GetType().FullName}"); Console.Error.WriteLine($"[LOADER][TRACE] reserve range: AllocateAt missing on {ctx.Memory.GetType().FullName}");
return false; return false;
} }
var executable = (protection & OrbisProtCpuExec) != 0;
var invokeArgs = allocateAtHasAllowAlternativeArg var invokeArgs = allocateAtHasAllowAlternativeArg
? new object[] { desiredAddress, length, executable, true } ? new object[] { desiredAddress, length, executable, false }
: new object[] { desiredAddress, length, executable }; : new object[] { desiredAddress, length, executable };
var result = allocateAt.Invoke(memoryObject, invokeArgs); var result = allocateAt.Invoke(memoryObject, invokeArgs);
if (result is not ulong allocated || allocated == 0) 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) 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<byte>(); bytes = Array.Empty<byte>();
if (address == 0) if (address == 0)
@@ -3966,26 +3991,65 @@ public static class KernelMemoryCompatExports
return false; return false;
} }
var limit = (int)Math.Min(maxLength, 1_048_576UL); var limit = (int)Math.Min(maxLength, (ulong)Math.Max(0, hardLimit));
var buffer = new List<byte>(Math.Min(limit, 256)); if (limit == 0)
Span<byte> one = stackalloc byte[1];
for (var i = 0; i < limit; i++)
{ {
if (!TryReadCompat(ctx, address + (ulong)i, one)) return true;
}
const int maxChunkSize = 4096;
var chunk = GC.AllocateUninitializedArray<byte>(Math.Min(maxChunkSize, limit));
var writer = new ArrayBufferWriter<byte>(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<byte> one = stackalloc byte[1];
if (!TryReadCompat(ctx, current, one))
{ {
return false; return false;
} }
if (one[0] == 0) if (one[0] == 0)
{ {
bytes = buffer.ToArray(); bytes = writer.WrittenSpan.ToArray();
return true; return true;
} }
buffer.Add(one[0]); one.CopyTo(writer.GetSpan(1));
writer.Advance(1);
offset++;
} }
bytes = buffer.ToArray(); bytes = writer.WrittenSpan.ToArray();
return true; return true;
} }
@@ -4165,25 +4229,12 @@ public static class KernelMemoryCompatExports
return false; return false;
} }
var buffer = new List<byte>(Math.Min(maxLength, 256)); if (!TryReadBytesUntilNull(ctx, address, (ulong)maxLength, maxLength, out var bytes))
Span<byte> one = stackalloc byte[1];
for (var i = 0; i < maxLength; i++)
{ {
if (!TryReadCompat(ctx, address + (ulong)i, one)) return false;
{
return false;
}
if (one[0] == 0)
{
value = Encoding.UTF8.GetString(buffer.ToArray());
return true;
}
buffer.Add(one[0]);
} }
value = Encoding.UTF8.GetString(buffer.ToArray()); value = Encoding.UTF8.GetString(bytes);
return true; return true;
} }
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE; using SharpEmu.HLE;
using SharpEmu.Libs.Fiber;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@@ -32,6 +33,8 @@ public static class KernelRuntimeCompatExports
private const ulong DefaultKernelTscFrequency = 10_000_000UL; private const ulong DefaultKernelTscFrequency = 10_000_000UL;
private const ulong PrtAreaStartAddress = 0x0000001000000000UL; private const ulong PrtAreaStartAddress = 0x0000001000000000UL;
private const ulong PrtAreaSize = 0x000000EC00000000UL; private const ulong PrtAreaSize = 0x000000EC00000000UL;
private const int MapFlagFixed = 0x10;
private const ulong DefaultVirtualRangeAlignment = 0x4000UL;
private const int AioInitParamSize = 0x3C; private const int AioInitParamSize = 0x3C;
private const uint MemCommit = 0x1000; private const uint MemCommit = 0x1000;
private const uint MemReserve = 0x2000; private const uint MemReserve = 0x2000;
@@ -73,6 +76,8 @@ public static class KernelRuntimeCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
GuestThreadExecution.Scheduler?.Pump(ctx, "sceKernelUsleep");
if (micros < 1000) if (micros < 1000)
{ {
Thread.Yield(); Thread.Yield();
@@ -108,8 +113,14 @@ public static class KernelRuntimeCompatExports
lockText = $"0x{lockValue:X16}"; 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( 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( [SysAbiExport(
@@ -653,7 +664,7 @@ public static class KernelRuntimeCompatExports
{ {
var inOutAddressPointer = ctx[CpuRegister.Rdi]; var inOutAddressPointer = ctx[CpuRegister.Rdi];
var length = ctx[CpuRegister.Rsi]; var length = ctx[CpuRegister.Rsi];
var _flags = ctx[CpuRegister.Rdx]; var flags = unchecked((int)ctx[CpuRegister.Rdx]);
var alignment = ctx[CpuRegister.Rcx]; var alignment = ctx[CpuRegister.Rcx];
if (inOutAddressPointer == 0 || length == 0) if (inOutAddressPointer == 0 || length == 0)
{ {
@@ -665,7 +676,8 @@ public static class KernelRuntimeCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; 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; ulong desiredAddress;
lock (_stateGate) lock (_stateGate)
{ {
@@ -674,13 +686,13 @@ public static class KernelRuntimeCompatExports
: AlignUp(_nextReservedVirtualBase, effectiveAlignment); : 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; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
Console.Error.WriteLine( 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)) if (!ctx.TryWriteUInt64(inOutAddressPointer, mappedAddress))
{ {
@@ -964,14 +976,12 @@ public static class KernelRuntimeCompatExports
public static int StackCheckFail(CpuContext ctx) public static int StackCheckFail(CpuContext ctx)
{ {
var count = Interlocked.Increment(ref _stackChkFailCount); var count = Interlocked.Increment(ref _stackChkFailCount);
if (count <= 8) Console.Error.WriteLine(
{ $"[LOADER][ERROR] __stack_chk_fail#{count}: rip=0x{ctx.Rip:X16} rdi=0x{ctx[CpuRegister.Rdi]:X16}");
Console.Error.WriteLine( var result = (int)OrbisGen2Result.ORBIS_GEN2_ERROR_CPU_TRAP;
$"[LOADER][WARNING] __stack_chk_fail recovery#{count}: rip=0x{ctx.Rip:X16} rdi=0x{ctx[CpuRegister.Rdi]:X16}"); GuestThreadExecution.RequestCurrentEntryExit("__stack_chk_fail", result);
} ctx[CpuRegister.Rax] = unchecked((ulong)result);
return result;
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport( [SysAbiExport(
@@ -1656,7 +1666,13 @@ public static class KernelRuntimeCompatExports
[DllImport("kernel32.dll", SetLastError = true)] [DllImport("kernel32.dll", SetLastError = true)]
private static extern nint VirtualAlloc(nint lpAddress, nuint dwSize, uint flAllocationType, uint flProtect); 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; mappedAddress = 0;
if (length == 0) if (length == 0)
@@ -1668,40 +1684,51 @@ public static class KernelRuntimeCompatExports
{ {
object memoryObject = ctx.Memory; object memoryObject = ctx.Memory;
MethodInfo? allocateAt = null; MethodInfo? allocateAt = null;
MethodInfo? allocateAtOrAbove = null;
var allocateAtHasAllowAlternativeArg = false; var allocateAtHasAllowAlternativeArg = false;
for (var depth = 0; depth < 4; depth++) for (var depth = 0; depth < 4; depth++)
{ {
foreach (var candidate in memoryObject.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)) foreach (var candidate in memoryObject.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance))
{ {
if (!string.Equals(candidate.Name, "AllocateAt", StringComparison.Ordinal))
{
continue;
}
var parameters = candidate.GetParameters(); var parameters = candidate.GetParameters();
if (parameters.Length == 3 && if (string.Equals(candidate.Name, "TryAllocateAtOrAbove", StringComparison.Ordinal) &&
parameters[0].ParameterType == typeof(ulong) && parameters.Length == 5 &&
parameters[1].ParameterType == typeof(ulong) &&
parameters[2].ParameterType == typeof(bool))
{
allocateAt = candidate;
allocateAtHasAllowAlternativeArg = false;
break;
}
if (parameters.Length == 4 &&
parameters[0].ParameterType == typeof(ulong) && parameters[0].ParameterType == typeof(ulong) &&
parameters[1].ParameterType == typeof(ulong) && parameters[1].ParameterType == typeof(ulong) &&
parameters[2].ParameterType == typeof(bool) && 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; break;
} }
} }
if (allocateAt is not null) if (allocateAtOrAbove is not null || allocateAt is not null)
{ {
break; break;
} }
@@ -1721,6 +1748,18 @@ public static class KernelRuntimeCompatExports
memoryObject = innerValue; 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) if (allocateAt is null)
{ {
Console.Error.WriteLine($"[LOADER][TRACE] reserve_virtual_range: AllocateAt missing on {ctx.Memory.GetType().FullName}"); 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 var invokeArgs = allocateAtHasAllowAlternativeArg
? new object[] { desiredAddress, length, false, true } ? new object[] { desiredAddress, length, false, false }
: new object[] { desiredAddress, length, false }; : new object[] { desiredAddress, length, false };
var result = allocateAt.Invoke(memoryObject, invokeArgs); var result = allocateAt.Invoke(memoryObject, invokeArgs);
if (result is not ulong allocated || allocated == 0) if (result is not ulong allocated || allocated == 0)