Files
sharpemu/src/SharpEmu.Core/Cpu/Native/StubManager.cs
T
ParantezTech 4d73f469bc initial commit
2026-03-11 15:48:28 +03:00

223 lines
6.3 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Runtime.InteropServices;
using SharpEmu.HLE;
namespace SharpEmu.Core.Cpu.Native;
public sealed unsafe class StubManager : IDisposable
{
private readonly List<nint> _allocatedStubs = new();
private readonly Dictionary<string, nint> _importHandlers = new();
private readonly Dictionary<ulong, nint> _stubAddresses = new();
private byte* _pltMemory;
private int _pltOffset;
private const int PltMemorySize = 1024 * 1024; // 1MB for stubs
public StubManager()
{
_pltMemory = (byte*)VirtualAlloc(
null,
(nuint)PltMemorySize,
AllocationType.Reserve | AllocationType.Commit,
MemoryProtection.ExecuteReadWrite);
if (_pltMemory == null)
{
throw new OutOfMemoryException("Failed to allocate executable memory for stubs");
}
_pltOffset = 0;
}
public nint CreateImportStub(ulong guestAddress, string nid, ImportHandler handler)
{
if (!_importHandlers.TryGetValue(nid, out var handlerPtr))
{
handlerPtr = CreateHandlerTrampoline(nid, handler);
_importHandlers[nid] = handlerPtr;
}
var stubPtr = _pltMemory + _pltOffset;
var stubIndex = (uint)(_stubAddresses.Count);
JitStubs.CreateJmpWithIndex(stubPtr, stubIndex, (void*)handlerPtr);
_pltOffset += JitStubs.JmpWithIndex.Size;
_stubAddresses[guestAddress] = (nint)stubPtr;
return (nint)stubPtr;
}
public nint CreatePltEntry(uint index, nint gotAddress)
{
var pltPtr = _pltMemory + _pltOffset;
var pltCode = new byte[]
{
0x49, 0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // movabs r11,<addr>
0x41, 0xFF, 0x73, 0x08, // push QWORD PTR [r11+8]
0x41, 0xFF, 0x63, 0x10, // jmp QWORD PTR [r11+16]
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 // nop padding
};
*(ulong*)(pltCode.AsSpan().Slice(2).GetPinnableReference()) = (ulong)gotAddress;
fixed (byte* src = pltCode)
{
Buffer.MemoryCopy(src, pltPtr, pltCode.Length, pltCode.Length);
}
_pltOffset += pltCode.Length;
return (nint)pltPtr;
}
public nint CreateTlsHandler(delegate*<void> tlsGetAddrFunc)
{
var handlerPtr = _pltMemory + _pltOffset;
JitStubs.CreateCall9(handlerPtr, (void*)tlsGetAddrFunc);
_pltOffset += JitStubs.Call9.Size;
return (nint)handlerPtr;
}
public nint CreateSafeCallTrampoline(delegate*<void> func, byte* regSaveArea, byte* lockVar)
{
var trampolinePtr = _pltMemory + _pltOffset;
var template = JitStubs.SafeCall.Template;
fixed (byte* src = template)
{
Buffer.MemoryCopy(src, trampolinePtr, template.Length, template.Length);
}
*(ulong*)(trampolinePtr + 0x0c + 2) = (ulong)lockVar;
*(ulong*)(trampolinePtr + 0x16 + 2) = (ulong)regSaveArea;
*(ulong*)(trampolinePtr + 0x20 + 2) = (ulong)func;
_pltOffset += template.Length;
return (nint)trampolinePtr;
}
public bool TryGetStubAddress(ulong guestAddress, out nint hostAddress)
{
return _stubAddresses.TryGetValue(guestAddress, out hostAddress);
}
public bool PatchTlsAccess(byte* guestCode, int codeSize, nint tlsHandler)
{
var locations = JitStubs.FindTlsAccessPatterns(guestCode, codeSize);
foreach (var location in locations)
{
JitStubs.CreateCall9((byte*)location, (void*)tlsHandler);
}
return locations.Count > 0;
}
private nint CreateHandlerTrampoline(string nid, ImportHandler handler)
{
var trampolinePtr = _pltMemory + _pltOffset;
var code = new List<byte>();
code.Add(0x57);
code.Add(0x56);
code.Add(0x55);
code.Add(0x53);
code.AddRange(new byte[] { 0x41, 0x54 });
code.AddRange(new byte[] { 0x41, 0x55 });
code.AddRange(new byte[] { 0x41, 0x56 });
code.AddRange(new byte[] { 0x41, 0x57 });
code.AddRange(new byte[] { 0x48, 0x83, 0xEC, 0x28 });
int contextOffset = code.Count;
code.AddRange(new byte[] { 0x48, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
int handlerOffset = code.Count;
code.AddRange(new byte[] { 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
code.AddRange(new byte[] { 0xFF, 0xD0 });
code.AddRange(new byte[] { 0x48, 0x83, 0xC4, 0x28 });
code.AddRange(new byte[] { 0x41, 0x5F });
code.AddRange(new byte[] { 0x41, 0x5E });
code.AddRange(new byte[] { 0x41, 0x5D });
code.AddRange(new byte[] { 0x41, 0x5C });
code.Add(0x5B);
code.Add(0x5D);
code.Add(0x5E);
code.Add(0x5F);
code.Add(0xC3);
var codeArray = code.ToArray();
fixed (byte* src = codeArray)
{
Buffer.MemoryCopy(src, trampolinePtr, codeArray.Length, codeArray.Length);
}
*(ulong*)(trampolinePtr + contextOffset + 2) = 0;
var handlerPtr = Marshal.GetFunctionPointerForDelegate(handler);
*(ulong*)(trampolinePtr + handlerOffset + 2) = (ulong)handlerPtr;
_pltOffset += codeArray.Length;
_allocatedStubs.Add((nint)trampolinePtr);
return (nint)trampolinePtr;
}
public void Dispose()
{
if (_pltMemory != null)
{
VirtualFree(_pltMemory, 0, FreeType.Release);
_pltMemory = null;
}
_allocatedStubs.Clear();
_importHandlers.Clear();
_stubAddresses.Clear();
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern void* VirtualAlloc(void* lpAddress, nuint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool VirtualFree(void* lpAddress, nuint dwSize, FreeType dwFreeType);
[Flags]
private enum AllocationType : uint
{
Commit = 0x1000,
Reserve = 0x2000,
}
[Flags]
private enum MemoryProtection : uint
{
ExecuteReadWrite = 0x40,
}
private enum FreeType : uint
{
Release = 0x8000,
}
public delegate void ImportHandler(CpuContext context);
}