mirror of
https://github.com/FEX-Emu/FEX.git
synced 2025-01-10 07:41:41 +00:00
SMC: Add --smc-full-checks, implement frontend, irint, irjit, unit test
This commit is contained in:
parent
c28a684305
commit
df3bdf361f
@ -36,6 +36,9 @@ namespace FEXCore::Config {
|
||||
case FEXCore::Config::CONFIG_TSO_ENABLED:
|
||||
CTX->Config.TSOEnabled = Config != 0;
|
||||
break;
|
||||
case FEXCore::Config::CONFIG_SMC_CHECKS:
|
||||
CTX->Config.SMCChecks = Config != 0;
|
||||
break;
|
||||
default: LogMan::Msg::A("Unknown configuration option");
|
||||
}
|
||||
}
|
||||
@ -83,6 +86,9 @@ namespace FEXCore::Config {
|
||||
case FEXCore::Config::CONFIG_TSO_ENABLED:
|
||||
return CTX->Config.TSOEnabled;
|
||||
break;
|
||||
case FEXCore::Config::CONFIG_SMC_CHECKS:
|
||||
return CTX->Config.SMCChecks;
|
||||
break;
|
||||
default: LogMan::Msg::A("Unknown configuration option");
|
||||
}
|
||||
|
||||
|
@ -60,6 +60,7 @@ namespace FEXCore::Context {
|
||||
bool Is64BitMode {true};
|
||||
uint64_t EmulatedCPUCores{1};
|
||||
bool TSOEnabled {true};
|
||||
bool SMCChecks {false};
|
||||
} Config;
|
||||
|
||||
FEXCore::Memory::MemMapper MemoryMapper;
|
||||
@ -113,6 +114,8 @@ namespace FEXCore::Context {
|
||||
void StopGdbServer();
|
||||
void HandleCallback(uint64_t RIP);
|
||||
|
||||
static void RemoveCodeEntry(FEXCore::Core::InternalThreadState *Thread, uint64_t GuestRIP);
|
||||
|
||||
// Debugger interface
|
||||
void CompileRIP(FEXCore::Core::InternalThreadState *Thread, uint64_t RIP);
|
||||
uint64_t GetThreadCount() const;
|
||||
|
37
External/FEXCore/Source/Interface/Core/Core.cpp
vendored
37
External/FEXCore/Source/Interface/Core/Core.cpp
vendored
@ -647,6 +647,37 @@ namespace FEXCore::Context {
|
||||
TableInfo = Block.DecodedInstructions[i].TableInfo;
|
||||
DecodedInfo = &Block.DecodedInstructions[i];
|
||||
|
||||
if (Config.SMCChecks) {
|
||||
__uint128_t existing;
|
||||
|
||||
uintptr_t ExistingCodePtr{};
|
||||
|
||||
if (Thread->CTX->Config.UnifiedMemory) {
|
||||
ExistingCodePtr = reinterpret_cast<uintptr_t>(Block.Entry + BlockInstructionsLength);
|
||||
}
|
||||
else {
|
||||
ExistingCodePtr = MemoryMapper.GetPointer<uintptr_t>(Block.Entry + BlockInstructionsLength);
|
||||
}
|
||||
|
||||
memcpy(&existing, (void*)(ExistingCodePtr), DecodedInfo->InstSize);
|
||||
auto CodeChanged = Thread->OpDispatcher->_ValidateCode(existing, ExistingCodePtr, DecodedInfo->InstSize);
|
||||
|
||||
auto InvalidateCodeCond = Thread->OpDispatcher->_CondJump(CodeChanged);
|
||||
|
||||
auto CodeWasChangedBlock = Thread->OpDispatcher->CreateNewCodeBlock();
|
||||
Thread->OpDispatcher->SetTrueJumpTarget(InvalidateCodeCond, CodeWasChangedBlock);
|
||||
|
||||
Thread->OpDispatcher->SetCurrentCodeBlock(CodeWasChangedBlock);
|
||||
Thread->OpDispatcher->_RemoveCodeEntry(GuestRIP);
|
||||
Thread->OpDispatcher->_StoreContext(IR::GPRClass, 8, offsetof(FEXCore::Core::CPUState, rip), Thread->OpDispatcher->_Constant(Block.Entry + BlockInstructionsLength));
|
||||
Thread->OpDispatcher->_ExitFunction();
|
||||
|
||||
auto NextOpBlock = Thread->OpDispatcher->CreateNewCodeBlock();
|
||||
|
||||
Thread->OpDispatcher->SetFalseJumpTarget(InvalidateCodeCond, NextOpBlock);
|
||||
Thread->OpDispatcher->SetCurrentCodeBlock(NextOpBlock);
|
||||
}
|
||||
|
||||
if (TableInfo->OpcodeDispatcher) {
|
||||
auto Fn = TableInfo->OpcodeDispatcher;
|
||||
std::invoke(Fn, Thread->OpDispatcher, DecodedInfo);
|
||||
@ -799,6 +830,12 @@ namespace FEXCore::Context {
|
||||
SignalDelegation.UninstallTLSState(Thread);
|
||||
}
|
||||
|
||||
void Context::RemoveCodeEntry(FEXCore::Core::InternalThreadState *Thread, uint64_t GuestRIP) {
|
||||
Thread->IRLists.erase(GuestRIP);
|
||||
Thread->DebugData.erase(GuestRIP);
|
||||
Thread->BlockCache->Erase(GuestRIP);
|
||||
}
|
||||
|
||||
// Debug interface
|
||||
void Context::CompileRIP(FEXCore::Core::InternalThreadState *Thread, uint64_t RIP) {
|
||||
uint64_t RIPBackup = Thread->State.State.rip;
|
||||
|
@ -138,6 +138,23 @@ void InterpreterCore::ExecuteCode(FEXCore::Core::InternalThreadState *Thread) {
|
||||
uint32_t Node = WrapperOp.ID();
|
||||
|
||||
switch (IROp->Op) {
|
||||
case IR::OP_VALIDATECODE: {
|
||||
auto Op = IROp->C<IR::IROp_ValidateCode>();
|
||||
|
||||
if (memcmp((void*)Op->CodePtr, &Op->CodeOriginal, Op->CodeLength) != 0) {
|
||||
GD = 1;
|
||||
} else {
|
||||
GD = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case IR::OP_REMOVECODEENTRY: {
|
||||
auto Op = IROp->C<IR::IROp_RemoveCodeEntry>();
|
||||
CTX->RemoveCodeEntry(Thread, Op->RIP);
|
||||
break;
|
||||
}
|
||||
|
||||
case IR::OP_DUMMY:
|
||||
case IR::OP_BEGINBLOCK:
|
||||
break;
|
||||
|
@ -115,6 +115,11 @@ DispatchGenerator::DispatchGenerator(FEXCore::Context::Context *ctx, FEXCore::Co
|
||||
|
||||
shl(rax, (int)log2(sizeof(FEXCore::BlockCache::BlockCacheEntry)));
|
||||
|
||||
// check for aliasing
|
||||
mov(rcx, qword [rdi + rax + 8]);
|
||||
cmp(rcx, rdx);
|
||||
jne(NoBlock);
|
||||
|
||||
// Load the block pointer
|
||||
mov(rax, qword [rdi + rax]);
|
||||
|
||||
|
@ -184,6 +184,82 @@ DEF_OP(Thunk) {
|
||||
add(sp, sp, SPOffset);
|
||||
}
|
||||
|
||||
|
||||
DEF_OP(ValidateCode) {
|
||||
auto Op = IROp->C<IR::IROp_ValidateCode>();
|
||||
uint8_t *NewCode = (uint8_t *)Op->CodePtr;
|
||||
uint8_t *OldCode = (uint8_t *)&Op->CodeOriginal;
|
||||
int len = Op->CodeLength;
|
||||
int idx = 0;
|
||||
|
||||
LoadConstant(GetReg<RA_64>(Node), 0);
|
||||
LoadConstant(x0, Op->CodePtr);
|
||||
LoadConstant(x1, 1);
|
||||
|
||||
while (len >= 4)
|
||||
{
|
||||
ldr(w2, MemOperand(x0, idx));
|
||||
LoadConstant(w3, *(uint32_t *)(OldCode + idx));
|
||||
cmp(w2, w3);
|
||||
csel(GetReg<RA_64>(Node), GetReg<RA_64>(Node), x1, Condition::eq);
|
||||
len -= 4;
|
||||
idx += 4;
|
||||
}
|
||||
while (len >= 2)
|
||||
{
|
||||
ldrh(w2, MemOperand(x0, idx));
|
||||
LoadConstant(w3, *(uint16_t *)(OldCode + idx));
|
||||
cmp(w2, w3);
|
||||
csel(GetReg<RA_64>(Node), GetReg<RA_64>(Node), x1, Condition::eq);
|
||||
len -= 2;
|
||||
idx += 2;
|
||||
}
|
||||
while (len >= 1)
|
||||
{
|
||||
ldrb(w2, MemOperand(x0, idx));
|
||||
LoadConstant(w3, *(uint8_t *)(OldCode + idx));
|
||||
cmp(w2, w3);
|
||||
csel(GetReg<RA_64>(Node), GetReg<RA_64>(Node), x1, Condition::eq);
|
||||
len -= 1;
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
DEF_OP(RemoveCodeEntry) {
|
||||
auto Op = IROp->C<IR::IROp_RemoveCodeEntry>();
|
||||
// Arguments are passed as follows:
|
||||
// X0: Thread
|
||||
// X1: RIP
|
||||
|
||||
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));
|
||||
|
||||
mov(x0, STATE);
|
||||
LoadConstant(x1, Op->RIP);
|
||||
|
||||
LoadConstant(x2, reinterpret_cast<uintptr_t>(&Context::Context::RemoveCodeEntry));
|
||||
blr(x2);
|
||||
|
||||
// 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);
|
||||
@ -243,6 +319,8 @@ void JITCore::RegisterBranchHandlers() {
|
||||
REGISTER_OP(CONDJUMP, CondJump);
|
||||
REGISTER_OP(SYSCALL, Syscall);
|
||||
REGISTER_OP(THUNK, Thunk);
|
||||
REGISTER_OP(VALIDATECODE, ValidateCode);
|
||||
REGISTER_OP(REMOVECODEENTRY, RemoveCodeEntry);
|
||||
REGISTER_OP(CPUID, CPUID);
|
||||
#undef REGISTER_OP
|
||||
}
|
||||
|
@ -309,6 +309,8 @@ private:
|
||||
DEF_OP(CondJump);
|
||||
DEF_OP(Syscall);
|
||||
DEF_OP(Thunk);
|
||||
DEF_OP(ValidateCode);
|
||||
DEF_OP(RemoveCodeEntry);
|
||||
DEF_OP(CPUID);
|
||||
|
||||
///< Conversion ops
|
||||
|
@ -4845,6 +4845,65 @@ void *JITCore::CompileCode([[maybe_unused]] FEXCore::IR::IRListView<true> const
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IR::OP_VALIDATECODE:
|
||||
{
|
||||
auto Op = IROp->C<IR::IROp_ValidateCode>();
|
||||
uint8_t* NewCode = (uint8_t*)Op->CodePtr;
|
||||
uint8_t* OldCode = (uint8_t*)&Op->CodeOriginal;
|
||||
int len = Op->CodeLength;
|
||||
int idx = 0;
|
||||
|
||||
xor_(GetDst<RA_64>(Node), GetDst<RA_64>(Node));
|
||||
mov(rax, Op->CodePtr);
|
||||
mov(rbx, 1);
|
||||
while (len >= 4) {
|
||||
cmp(dword[rax + idx], *(uint32_t*)(OldCode + idx));
|
||||
cmovne(GetDst<RA_64>(Node), rbx);
|
||||
len-=4;
|
||||
idx+=4;
|
||||
}
|
||||
while (len >= 2) {
|
||||
mov(rcx, *(uint16_t*)(OldCode + idx));
|
||||
cmp(word[rax + idx], cx);
|
||||
cmovne(GetDst<RA_64>(Node), rbx);
|
||||
len-=2;
|
||||
idx+=2;
|
||||
}
|
||||
while (len >= 1) {
|
||||
cmp(byte[rax + idx], *(uint8_t*)(OldCode + idx));
|
||||
cmovne(GetDst<RA_64>(Node), rbx);
|
||||
len-=1;
|
||||
idx+=1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IR::OP_REMOVECODEENTRY: {
|
||||
auto Op = IROp->C<IR::IROp_RemoveCodeEntry>();
|
||||
|
||||
auto NumPush = RA64.size();
|
||||
|
||||
for (auto &Reg : RA64)
|
||||
push(Reg);
|
||||
|
||||
if (NumPush & 1)
|
||||
sub(rsp, 8); // Align
|
||||
|
||||
mov(rdi, STATE);
|
||||
mov(rax, Op->RIP); // imm64 move
|
||||
mov(rsi, rax);
|
||||
|
||||
|
||||
mov(rax, reinterpret_cast<uintptr_t>(&Context::Context::RemoveCodeEntry));
|
||||
call(rax);
|
||||
|
||||
if (NumPush & 1)
|
||||
add(rsp, 8); // Align
|
||||
|
||||
for (uint32_t i = RA64.size(); i > 0; --i)
|
||||
pop(RA64[i - 1]);
|
||||
|
||||
break;
|
||||
}
|
||||
case IR::OP_DUMMY:
|
||||
case IR::OP_IRHEADER:
|
||||
case IR::OP_PHIVALUE:
|
||||
@ -4971,6 +5030,11 @@ void JITCore::CreateCustomDispatch(FEXCore::Core::InternalThreadState *Thread) {
|
||||
|
||||
shl(rax, (int)log2(sizeof(FEXCore::BlockCache::BlockCacheEntry)));
|
||||
|
||||
// check for aliasing
|
||||
mov(rcx, qword [rdi + rax + 8]);
|
||||
cmp(rcx, rdx);
|
||||
jne(NoBlock);
|
||||
|
||||
// Load the block pointer
|
||||
mov(rax, qword [rdi + rax]);
|
||||
|
||||
|
21
External/FEXCore/Source/Interface/IR/IR.json
vendored
21
External/FEXCore/Source/Interface/IR/IR.json
vendored
@ -86,6 +86,27 @@
|
||||
]
|
||||
},
|
||||
|
||||
"ValidateCode": {
|
||||
"HasSideEffects": true,
|
||||
"OpClass": "Misc",
|
||||
"HasDest": true,
|
||||
"DestClass": "GPR",
|
||||
"DestSize": "8",
|
||||
"Args": [
|
||||
"__uint128_t", "CodeOriginal",
|
||||
"uint64_t", "CodePtr",
|
||||
"uint8_t", "CodeLength"
|
||||
]
|
||||
},
|
||||
|
||||
"RemoveCodeEntry": {
|
||||
"HasSideEffects": true,
|
||||
"OpClass": "Misc",
|
||||
"Args": [
|
||||
"uint64_t", "RIP"
|
||||
]
|
||||
},
|
||||
|
||||
"GuestCallDirect": {
|
||||
"OpClass": "Branch",
|
||||
"Args": [
|
||||
|
@ -17,6 +17,7 @@ namespace FEXCore::Config {
|
||||
CONFIG_IS64BIT_MODE,
|
||||
CONFIG_EMULATED_CPU_CORES,
|
||||
CONFIG_TSO_ENABLED,
|
||||
CONFIG_SMC_CHECKS
|
||||
};
|
||||
|
||||
enum ConfigCore {
|
||||
|
@ -68,6 +68,12 @@ namespace FEX::ArgLoader {
|
||||
.help("Disables TSO IR ops. Highly likely to break any threaded application")
|
||||
.set_default(true);
|
||||
|
||||
CPUGroup.add_option("--smc-full-checks")
|
||||
.dest("SMCChecks")
|
||||
.action("store_true")
|
||||
.help("Checks code for modification before execution. Slow.")
|
||||
.set_default(false);
|
||||
|
||||
Parser.add_option_group(CPUGroup);
|
||||
}
|
||||
{
|
||||
@ -190,6 +196,11 @@ namespace FEX::ArgLoader {
|
||||
bool TSOEnabled = Options.get("TSOEnabled");
|
||||
Config::Add("TSOEnabled", std::to_string(TSOEnabled));
|
||||
}
|
||||
|
||||
if (Options.is_set_by_user("SMCChecks")) {
|
||||
bool SMCChecks = Options.get("SMCChecks");
|
||||
Config::Add("SMCChecks", std::to_string(SMCChecks));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -81,6 +81,10 @@ namespace FEX::EnvLoader {
|
||||
if ((Value = GetVar("FEX_TSO_ENABLED")).size()) {
|
||||
if (isdigit(Value[0])) Config::Add("TSOEnabled", Value);
|
||||
}
|
||||
|
||||
if ((Value = GetVar("FEX_SMC_CHECKS")).size()) {
|
||||
if (isdigit(Value[0])) Config::Add("SMCChecks", Value);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -110,6 +110,7 @@ int main(int argc, char **argv, char **const envp) {
|
||||
FEX::Config::Value<std::string> Environment{"Env", ""};
|
||||
FEX::Config::Value<std::string> OutputLog{"OutputLog", "stderr"};
|
||||
FEX::Config::Value<bool> TSOEnabledConfig{"TSOEnabled", true};
|
||||
FEX::Config::Value<bool> SMCChecksConfig{"SMCChecks", false};
|
||||
|
||||
::SilentLog = SilentLog();
|
||||
|
||||
@ -154,6 +155,8 @@ int main(int argc, char **argv, char **const envp) {
|
||||
FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_IS64BIT_MODE, Loader.Is64BitMode());
|
||||
FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_EMULATED_CPU_CORES, ThreadsConfig());
|
||||
FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_TSO_ENABLED, TSOEnabledConfig());
|
||||
FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_SMC_CHECKS, SMCChecksConfig());
|
||||
|
||||
FEXCore::Context::SetCustomCPUBackendFactory(CTX, VMFactory::CPUCreationFactory);
|
||||
// FEXCore::Context::SetFallbackCPUBackendFactory(CTX, VMFactory::CPUCreationFactoryFallback);
|
||||
|
||||
|
@ -60,6 +60,7 @@ int main(int argc, char **argv, char **const envp) {
|
||||
FEX::Config::Value<uint64_t> BlockSizeConfig{"MaxInst", 1};
|
||||
FEX::Config::Value<bool> SingleStepConfig{"SingleStep", false};
|
||||
FEX::Config::Value<bool> MultiblockConfig{"Multiblock", false};
|
||||
FEX::Config::Value<bool> SMCChecksConfig{"SMCChecks", false};
|
||||
|
||||
auto Args = FEX::ArgLoader::Get();
|
||||
|
||||
@ -78,6 +79,7 @@ int main(int argc, char **argv, char **const envp) {
|
||||
FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_SINGLESTEP, SingleStepConfig());
|
||||
FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_MAXBLOCKINST, BlockSizeConfig());
|
||||
FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_IS64BIT_MODE, Loader.Is64BitMode());
|
||||
FEXCore::Config::SetConfig(CTX, FEXCore::Config::CONFIG_SMC_CHECKS, SMCChecksConfig());
|
||||
FEXCore::Context::SetCustomCPUBackendFactory(CTX, VMFactory::CPUCreationFactory);
|
||||
|
||||
FEXCore::Context::AddGuestMemoryRegion(CTX, SHM);
|
||||
|
@ -61,6 +61,11 @@ foreach(ASM_SRC ${ASM_SOURCES})
|
||||
|
||||
set(TEST_NAME "${TEST_DESC}/Test_${REL_TEST_ASM}")
|
||||
string(REPLACE " " ";" ARGS_LIST ${ARGS})
|
||||
|
||||
if (TEST_NAME MATCHES "SelfModifyingCode")
|
||||
list(APPEND ARGS_LIST "--smc-full-checks")
|
||||
endif()
|
||||
|
||||
add_test(NAME ${TEST_NAME}
|
||||
COMMAND "python3" "${CMAKE_SOURCE_DIR}/Scripts/testharness_runner.py"
|
||||
"${CMAKE_SOURCE_DIR}/unittests/ASM/Known_Failures"
|
||||
|
26
unittests/ASM/SelfModifyingCode/DifferentBlock.asm
Normal file
26
unittests/ASM/SelfModifyingCode/DifferentBlock.asm
Normal file
@ -0,0 +1,26 @@
|
||||
%ifdef CONFIG
|
||||
{
|
||||
"Match": "All",
|
||||
"RegData": {
|
||||
"RAX": "0x20"
|
||||
}
|
||||
}
|
||||
%endif
|
||||
|
||||
jmp main
|
||||
|
||||
patched_op:
|
||||
mov rax,-1
|
||||
ret
|
||||
|
||||
main:
|
||||
|
||||
; warm up the cache
|
||||
call patched_op
|
||||
|
||||
mov byte [rel patched_op], 0xC3
|
||||
|
||||
mov rax, 32
|
||||
call patched_op
|
||||
|
||||
hlt
|
28
unittests/ASM/SelfModifyingCode/SameBlock.asm
Normal file
28
unittests/ASM/SelfModifyingCode/SameBlock.asm
Normal file
@ -0,0 +1,28 @@
|
||||
%ifdef CONFIG
|
||||
{
|
||||
"Match": "All",
|
||||
"RegData": {
|
||||
"RAX": "0x20"
|
||||
}
|
||||
}
|
||||
%endif
|
||||
|
||||
|
||||
mov rax, 32
|
||||
|
||||
; patch mov rax,... to nops
|
||||
mov byte [rel patched_op + 0], 0x90
|
||||
mov byte [rel patched_op + 1], 0x90
|
||||
mov byte [rel patched_op + 2], 0x90
|
||||
mov byte [rel patched_op + 3], 0x90
|
||||
mov byte [rel patched_op + 4], 0x90
|
||||
mov byte [rel patched_op + 5], 0x90
|
||||
mov byte [rel patched_op + 6], 0x90
|
||||
mov byte [rel patched_op + 7], 0x90
|
||||
mov byte [rel patched_op + 8], 0x90
|
||||
mov byte [rel patched_op + 9], 0x90
|
||||
|
||||
patched_op:
|
||||
mov rax,0xFABCFABCFABC0123 ; 10 bytes long
|
||||
|
||||
hlt
|
Loading…
Reference in New Issue
Block a user