mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-25 20:28:48 +08:00
223 lines
6.3 KiB
C#
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);
|
|
}
|