Merge pull request #281 from FEX-Emu/skmp/native-lib-thunking-3

Thunks: ALTINST, OP_THUNK, basic infrastructure
This commit is contained in:
Ryan Houdek authored and GitHub committed 2020-07-20 13:36:13 -07:00
commit f397bf3349
20 files changed
+337 -1

No files matched your search

+1
View File
@@ -164,6 +164,7 @@ set (SRCS
Interface/HLE/Syscalls/Timer.cpp
Interface/HLE/Syscalls/NotImplemented.cpp
Interface/HLE/Syscalls/Stubs.cpp
Interface/HLE/Thunks/Thunks.cpp
Interface/Memory/MemMapper.cpp
Interface/Memory/SharedMem.cpp
Interface/IR/IR.cpp
+3
View File
@@ -42,6 +42,9 @@ namespace FEXCore::Config {
case CONFIG_ROOTFSPATH:
CTX->Config.RootFSPath = Config;
break;
case CONFIG_THUNKLIBSPATH:
CTX->Config.ThunkLibsPath = Config;
break;
default: LogMan::Msg::A("Unknown configuration option");
}
}
+4
View File
@@ -17,6 +17,7 @@
namespace FEXCore {
class SyscallHandler;
class ThunkHandler;
class BlockSamplingData;
class GdbServer;
@@ -52,6 +53,7 @@ namespace FEXCore::Context {
bool GdbServer {false};
bool UnifiedMemory {true};
std::string RootFSPath;
std::string ThunkLibsPath;
bool Is64BitMode {true};
uint64_t EmulatedCPUCores{1};
@@ -80,6 +82,8 @@ namespace FEXCore::Context {
FEXCore::CPUIDEmu CPUID;
std::unique_ptr<FEXCore::SyscallHandler> SyscallHandler;
std::unique_ptr<FEXCore::ThunkHandler> ThunkHandler;
CustomCPUFactoryType CustomCPUFactory;
CustomCPUFactoryType FallbackCPUFactory;
std::function<void(uint64_t ThreadId, FEXCore::Context::ExitReason)> CustomExitHandler;
+4
View File
@@ -19,6 +19,8 @@
#include <FEXCore/Core/CPUBackend.h>
#include <FEXCore/Core/X86Enums.h>
#include "Interface/HLE/Thunks/Thunks.h"
#include <fstream>
#include <unistd.h>
@@ -229,6 +231,8 @@ namespace FEXCore::Context {
bool Context::InitCore(FEXCore::CodeLoader *Loader) {
SyscallHandler.reset(FEXCore::CreateHandler(Config.Is64BitMode ? OperatingMode::MODE_64BIT : OperatingMode::MODE_32BIT, this));
ThunkHandler.reset(FEXCore::ThunkHandler::Create());
LocalLoader = Loader;
using namespace FEXCore::Core;
FEXCore::Core::CPUState NewThreadState{};
@@ -11,6 +11,8 @@
#include <FEXCore/IR/IR.h>
#include <FEXCore/IR/IntrusiveIRList.h>
#include "Interface/HLE/Thunks/Thunks.h"
#include <atomic>
#include <cmath>
#include <limits>
@@ -248,6 +250,15 @@ void InterpreterCore::ExecuteCode(FEXCore::Core::InternalThreadState *Thread) {
GD = Res;
break;
}
case IR::OP_THUNK: {
auto Op = IROp->C<IR::IROp_Thunk>();
//LogMan::Msg::D("Thunk function: %s, %p, %p\n", Op->ThunkName, Op->ThunkFnPtr, *GetSrc<void**>(Op->Header.Args[0]));
reinterpret_cast<ThunkedFunction*>(Op->ThunkFnPtr)(CTX, *GetSrc<void**>(Op->Header.Args[0]));
break;
}
case IR::OP_CPUID: {
auto Op = IROp->C<IR::IROp_CPUID>();
uint64_t *DstPtr = GetDest<uint64_t*>(*WrapperOp);
@@ -123,6 +123,45 @@ DEF_OP(Syscall) {
add(sp, sp, SPOffset);
}
DEF_OP(Thunk) {
auto Op = IROp->C<IR::IROp_Thunk>();
// Arguments are passed as follows:
// X0: CTX
// X1: Args (from guest stack)
uint64_t SPOffset = AlignUp((RA64.size() + 1) * 8, 16);
sub(sp, sp, SPOffset);
int i = 0;
for (auto RA : RA64) {
str(RA, MemOperand(sp, i * 8));
i++;
}
str(lr, MemOperand(sp, RA64.size() * 8 + 0 * 8));
LoadConstant(x0, reinterpret_cast<uint64_t>(CTX));
mov(x1, GetReg<RA_64>(Op->Header.Args[0].ID()));
#if _M_X86_64
ERROR_AND_DIE("JIT: OP_THUNK not supported with arm simulator")
#else
LoadConstant(x2, Op->ThunkFnPtr);
blr(x2);
#endif
// Fix the stack and any values that were stepped on
i = 0;
for (auto RA : RA64) {
ldr(RA, MemOperand(sp, i * 8));
i++;
}
ldr(lr, MemOperand(sp, RA64.size() * 8 + 0 * 8));
add(sp, sp, SPOffset);
}
DEF_OP(CPUID) {
auto Op = IROp->C<IR::IROp_CPUID>();
uint64_t SPOffset = AlignUp((RA64.size() + 2 + 2) * 8, 16);
@@ -173,6 +212,7 @@ void JITCore::RegisterBranchHandlers() {
REGISTER_OP(JUMP, Jump);
REGISTER_OP(CONDJUMP, CondJump);
REGISTER_OP(SYSCALL, Syscall);
REGISTER_OP(THUNK, Thunk);
REGISTER_OP(CPUID, CPUID);
#undef REGISTER_OP
}
@@ -277,6 +277,7 @@ private:
DEF_OP(Jump);
DEF_OP(CondJump);
DEF_OP(Syscall);
DEF_OP(Thunk);
DEF_OP(CPUID);
///< Conversion ops
@@ -2006,6 +2006,33 @@ void *JITCore::CompileCode([[maybe_unused]] FEXCore::IR::IRListView<true> const
mov (GetDst<RA_64>(Node), rax);
break;
}
case IR::OP_THUNK: {
auto Op = IROp->C<IR::IROp_Thunk>();
auto NumPush = 1 + RA64.size();
push(rdi);
for (auto &Reg : RA64)
push(Reg);
if (NumPush & 1)
sub(rsp, 8); // Align
mov(rdi, reinterpret_cast<uintptr_t>(CTX));
mov(rsi, GetSrc<RA_64>(Op->Header.Args[0].ID()));
mov(rax, reinterpret_cast<uintptr_t>(Op->ThunkFnPtr));
call(rax);
if (NumPush & 1)
add(rsp, 8); // Align
for (uint32_t i = RA64.size(); i > 0; --i)
pop(RA64[i - 1]);
pop(rdi);
break;
}
case IR::OP_VEXTRACTTOGPR: {
auto Op = IROp->C<IR::IROp_VExtractToGPR>();
@@ -106,6 +106,7 @@ private:
struct LLVMCurrentState {
llvm::Function *SyscallFunction;
llvm::Function *ThunkFunction;
llvm::Function *CPUIDFunction;
llvm::Function *ExitVMFunction;
llvm::Function *ValuePrinter;
@@ -991,6 +992,31 @@ void LLVMJITCore::CreateGlobalVariables(llvm::ExecutionEngine *Engine, llvm::Mod
Engine->addGlobalMapping(JITCurrentState.SyscallFunction, Ptr.Data);
}
// Thunk Function
{
auto FuncType = FunctionType::get(voidTy,
{
i64, // Technically a this pointer
i64, // Technically a this pointer
i64, // Technically a this pointer
},
false);
JITCurrentState.ThunkFunction = Function::Create(FuncType,
Function::ExternalLinkage,
"ThunkCaller",
FunctionModule);
using ClassPtrType = void (*)(void (*)(void*, void*), void*, void*);
union PtrCast {
ClassPtrType ClassPtr;
void* Data;
};
PtrCast Ptr;
Ptr.ClassPtr = [](void(*fn)(void*, void*), void* ctx, void* args) {
fn(ctx, args);
};
Engine->addGlobalMapping(JITCurrentState.ThunkFunction, Ptr.Data);
}
// CPUID Function
{
auto FuncType = FunctionType::get(i128,
@@ -1687,6 +1713,17 @@ void LLVMJITCore::HandleIR(FEXCore::IR::IRListView<true> const *IR, IR::NodeWrap
SetDest(*WrapperOp, Result);
break;
}
case IR::OP_THUNK: {
auto Op = IROp->C<IR::IROp_Thunk>();
std::vector<llvm::Value*> Args{};
Args.emplace_back(JITState.IRBuilder->getInt64(Op->ThunkFnPtr)); // function to call
Args.emplace_back(JITState.IRBuilder->getInt64(reinterpret_cast<uint64_t>(CTX))); // context
Args.emplace_back(GetSrc(Op->Header.Args[0])); // params
JITState.IRBuilder->CreateCall(JITCurrentState.ThunkFunction, Args);
break;
}
case IR::OP_CPUID: {
auto Op = IROp->C<IR::IROp_CPUID>();
auto Src = GetSrc(Op->Header.Args[0]);
@@ -1,5 +1,7 @@
#include "Interface/Context/Context.h"
#include "Interface/Core/OpcodeDispatcher.h"
#include "Interface/HLE/Thunks/Thunks.h"
#include <FEXCore/Core/CoreState.h>
#include <climits>
#include <cstddef>
@@ -85,6 +87,41 @@ void OpDispatchBuilder::SyscallOp(OpcodeArgs) {
_StoreContext(GPRClass, GPRSize, offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_RAX]), SyscallOp);
}
void OpDispatchBuilder::ThunkOp(OpcodeArgs) {
uint8_t GPRSize = CTX->Config.Is64BitMode ? 8 : 4;
const char *name;
if (CTX->Config.UnifiedMemory) {
name = (const char*)(Op->PC + 2);
} else {
name = CTX->MemoryMapper.GetPointer<const char*>(Op->PC + 2);
}
_Thunk(
_LoadContext(GPRSize, offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_RDI]), GPRClass),
name,
(uintptr_t)CTX->ThunkHandler->LookupThunk(name)
);
auto Constant = _Constant(GPRSize);
auto OldSP = _LoadContext(GPRSize, offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_RSP]), GPRClass);
auto NewRIP = _LoadMem(GPRClass, GPRSize, OldSP, GPRSize);
OrderedNode *NewSP;
NewSP = _Add(OldSP, Constant);
// Store the new stack pointer
_StoreContext(GPRClass, GPRSize, offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_RSP]), NewSP);
// Store the new RIP
_StoreContext(GPRClass, GPRSize, offsetof(FEXCore::Core::CPUState, rip), NewRIP);
_ExitFunction();
BlockSetRIP = true;
}
void OpDispatchBuilder::LEAOp(OpcodeArgs) {
if (CTX->Config.Is64BitMode) {
uint32_t DstSize = X86Tables::DecodeFlags::GetOpAddr(Op->Flags, 0) == X86Tables::DecodeFlags::FLAG_OPERAND_SIZE_LAST ? 2 :
@@ -7082,6 +7119,7 @@ void InstallOpcodeHandlers(Context::OperatingMode Mode) {
{0x31, 1, &OpDispatchBuilder::RDTSCOp},
{0x3F, 1, &OpDispatchBuilder::ThunkOp},
{0x40, 16, &OpDispatchBuilder::CMOVOp},
{0x6E, 1, &OpDispatchBuilder::MOVBetweenGPR_FPR},
{0x6F, 1, &OpDispatchBuilder::MOVUPSOp},
@@ -100,6 +100,7 @@ public:
void ALUOp(OpcodeArgs);
void INTOp(OpcodeArgs);
void SyscallOp(OpcodeArgs);
void ThunkOp(OpcodeArgs);
void LEAOp(OpcodeArgs);
void NOPOp(OpcodeArgs);
void RETOp(OpcodeArgs);
@@ -57,7 +57,7 @@ void InitializeSecondaryTables() {
{0x38, 1, X86InstInfo{"", TYPE_0F38_TABLE, FLAGS_NO_OVERLAY, 0, nullptr}},
{0x39, 1, X86InstInfo{"", TYPE_INVALID, FLAGS_NO_OVERLAY, 0, nullptr}},
{0x3A, 1, X86InstInfo{"", TYPE_0F3A_TABLE, FLAGS_NO_OVERLAY, 0, nullptr}},
{0x3B, 5, X86InstInfo{"", TYPE_INVALID, FLAGS_NO_OVERLAY, 0, nullptr}},
{0x3B, 4, X86InstInfo{"", TYPE_INVALID, FLAGS_NO_OVERLAY, 0, nullptr}},
{0x40, 1, X86InstInfo{"CMOVO", TYPE_INST, FLAGS_MODRM | FLAGS_NO_OVERLAY, 0, nullptr}},
{0x41, 1, X86InstInfo{"CMOVNO", TYPE_INST, FLAGS_MODRM | FLAGS_NO_OVERLAY, 0, nullptr}},
@@ -248,6 +248,12 @@ void InitializeSecondaryTables() {
{0xFD, 1, X86InstInfo{"PADDW", TYPE_INST, GenFlagsSameSize(SIZE_64BIT) | FLAGS_MODRM | FLAGS_XMM_FLAGS | FLAGS_SF_MMX, 0, nullptr}},
{0xFE, 1, X86InstInfo{"PADDD", TYPE_INST, GenFlagsSameSize(SIZE_64BIT) | FLAGS_MODRM | FLAGS_XMM_FLAGS | FLAGS_SF_MMX, 0, nullptr}},
{0xFF, 1, X86InstInfo{"UD0", TYPE_INST, FLAGS_DEBUG | FLAGS_BLOCK_END, 0, nullptr}},
// FEX reserved instructions
// Unused x86 encoding instruction.
// This was originally used by VIA to jump to its alternative instruction set. Used for OP_THUNK
{0x3F, 1, X86InstInfo{"ALTINST", TYPE_INST, FLAGS_BLOCK_END | FLAGS_NO_OVERLAY | FLAGS_SETS_RIP, 0, nullptr}},
};
const U8U8InfoStruct RepModOpTable[] = {
+92
View File
@@ -0,0 +1,92 @@
#include "Thunks.h"
#include <LogManager.h>
#include "stdio.h"
#include <dlfcn.h>
#include <string>
#include <map>
#include <Interface/Context/Context.h>
#include <LogManager.h>
#include <mutex>
#include <shared_mutex>
namespace FEXCore {
struct ExportEntry { const char* Name; ThunkedFunction* Fn; };
class ThunkHandler_impl final: public ThunkHandler {
std::shared_mutex ThunksMutex;
std::map<std::string, ThunkedFunction*> Thunks = {
{ "fex:loadlib", &loadlib}
};
static void loadlib(FEXCore::Context::Context *CTX, void* Name) {
if (!CTX->Config.UnifiedMemory) {
Name = CTX->MemoryMapper.GetPointer<void*>((uintptr_t)Name);
}
auto SOName = CTX->Config.ThunkLibsPath + "/" + (const char*)Name + "-host.so";
LogMan::Msg::D("Load lib: %s -> %s", Name, SOName.c_str());
auto Handle = dlopen(SOName.c_str(), RTLD_LOCAL | RTLD_NOW);
if (!Handle) {
LogMan::Msg::E("Load lib: failed to dlopen %s", SOName.c_str());
return;
}
ExportEntry* (*InitFN)();
auto InitSym = std::string("fexthunks_exports_") + (const char*)Name;
(void*&)InitFN = dlsym(Handle, InitSym.c_str());
if (!InitFN) {
LogMan::Msg::E("Load lib: failed to find export %s", InitSym.c_str());
return;
}
auto Exports = InitFN();
auto That = reinterpret_cast<ThunkHandler_impl*>(CTX->ThunkHandler.get());
{
std::unique_lock lk(That->ThunksMutex);
int i;
for (i = 0; Exports[i].Name; i++) {
That->Thunks[Exports[i].Name] = Exports[i].Fn;
}
LogMan::Msg::D("Loaded %d syms", i);
}
}
public:
ThunkedFunction* LookupThunk(const char *Name) {
std::shared_lock lk(ThunksMutex);
auto it = Thunks.find(Name);
if (it != Thunks.end()) {
return it->second;
} else {
return nullptr;
}
}
ThunkHandler_impl() {
}
};
ThunkHandler* ThunkHandler::Create() {
return new ThunkHandler_impl();
}
}
+15
View File
@@ -0,0 +1,15 @@
namespace FEXCore::Context {
struct Context;
}
namespace FEXCore {
typedef void ThunkedFunction(FEXCore::Context::Context *CTX, void* ArgsRv);
class ThunkHandler {
public:
virtual ThunkedFunction* LookupThunk(const char *name) = 0;
virtual ~ThunkHandler() { }
static ThunkHandler* Create();
};
};
+4
View File
@@ -15,6 +15,10 @@ static void PrintArg(std::stringstream *out, [[maybe_unused]] IRListView<false>
*out << "#0x" << std::hex << Arg;
}
static void PrintArg(std::stringstream *out, [[maybe_unused]] IRListView<false> const* IR, const char* Arg) {
*out << Arg;
}
static void PrintArg(std::stringstream *out, [[maybe_unused]] IRListView<false> const* IR, CondClassType Arg) {
std::array<std::string, 14> CondNames = {
"EQ",
+13
View File
@@ -483,6 +483,19 @@
]
},
"Thunk": {
"HasSideEffects": true,
"OpClass": "Branch",
"SSAArgs": "1",
"SSANames": [
"ArgPtr"
],
"Args":[
"const char*", "ThunkName",
"uintptr_t", "ThunkFnPtr"
]
},
"LoadMem": {
"OpClass": "Memory",
"HasDest": true,
+1
View File
@@ -12,6 +12,7 @@ namespace FEXCore::Config {
CONFIG_SINGLESTEP,
CONFIG_GDBSERVER,
CONFIG_ROOTFSPATH,
CONFIG_THUNKLIBSPATH,
CONFIG_UNIFIED_MEMORY,
CONFIG_IS64BIT_MODE,
CONFIG_EMULATED_CPU_CORES,
+10
View File
@@ -66,11 +66,16 @@ namespace FEX::ArgLoader {
}
{
Parser.set_defaults("RootFS", "");
Parser.set_defaults("ThunkLibs", "");
EmulationGroup.add_option("-R", "--rootfs")
.dest("RootFS")
.help("Which Root filesystem prefix to use");
EmulationGroup.add_option("-t", "--thunklibs")
.dest("ThunkLibs")
.help("Folder to find the host-side thunking libs");
EmulationGroup.add_option("-U", "--unified-memory")
.dest("UnifiedMemory")
.action("store_true")
@@ -177,6 +182,11 @@ namespace FEX::ArgLoader {
Config::Add("RootFS", Option);
}
if (Options.is_set_by_user("ThunkLibs")) {
std::string Option = Options["ThunkLibs"];
Config::Add("ThunkLibs", Option);
}
if (Options.is_set_by_user("UnifiedMemory")) {
bool Option = Options.get("UnifiedMemory");
Config::Add("UnifiedMemory", std::to_string(Option));
+2
View File
@@ -101,6 +101,7 @@ int main(int argc, char **argv, char **const envp) {
FEX::Config::Value<uint64_t> ThreadsConfig{"Threads", 1};
FEX::Config::Value<bool> UnifiedMemory{"UnifiedMemory", true};
FEX::Config::Value<std::string> LDPath{"RootFS", ""};
FEX::Config::Value<std::string> ThunkLibsPath{"ThunkLibs", ""};
FEX::Config::Value<bool> SilentLog{"SilentLog", false};
FEX::Config::Value<std::string> Environment{"Env", ""};
@@ -129,6 +130,7 @@ int main(int argc, char **argv, char **const envp) {
FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_MAXBLOCKINST, BlockSizeConfig());
FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_GDBSERVER, GdbServerConfig());
FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_ROOTFSPATH, LDPath());
FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_THUNKLIBSPATH, ThunkLibsPath());
FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_UNIFIED_MEMORY, UnifiedMemory());
FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_IS64BIT_MODE, Loader.Is64BitMode());
FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_EMULATED_CPU_CORES, ThreadsConfig());
+26
View File
@@ -0,0 +1,26 @@
%ifdef CONFIG
{
"RegData": {
},
"MemoryRegions": {
"0x100000000": "4096"
}
}
%endif
; this just ensures the thunking system runs
; it doesn't actually load a lib
lea rdi, [rel libname]
lea rax, [rel thunk_test]
call rax
hlt
thunk_test:
db 0xf, 0x3f
db 'fex:loadlib'
db 0
libname db 'not_a_real_lib'
db 0