mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-14 01:00:43 +02:00
x86 has six instructions that will fault on us that we mostly handled. A few of these weren't being handled correctly. One problem is that RIP needs to synchronize differently depending on which fault instruction it is. Some instructions fault at the instruction RIP, some at the instruction afterwards. Additionally some of the metadata generated around the signal delegation wasn't correct. With behaviour of all of these instructions changed, it will now be easier to switch over to non-faulting guest synchronous signals off of these instructions. This doesn't go far enough to change that behaviour yet. I noticed this when looking at Elden Ring's weird faulting behaviour with ud2 and `int 0x2d`. This made me investigate since I had a suspicion that we weren't handling both cases correctly. With this change, Elden Ring is now stabilized and works under FEX.
242 lines
7.1 KiB
C++
242 lines
7.1 KiB
C++
/*
|
|
$info$
|
|
tags: Bin|TestHarnessRunner
|
|
desc: Used to run Assembly tests
|
|
$end_info$
|
|
*/
|
|
|
|
#include "Common/ArgumentLoader.h"
|
|
#include "TestHarnessRunner/HostRunner.h"
|
|
#include "HarnessHelpers.h"
|
|
#include "Tests/LinuxSyscalls/LinuxAllocator.h"
|
|
#include "Tests/LinuxSyscalls/Syscalls.h"
|
|
#include "Tests/LinuxSyscalls/x32/Syscalls.h"
|
|
#include "Tests/LinuxSyscalls/x64/Syscalls.h"
|
|
#include "Tests/LinuxSyscalls/SignalDelegator.h"
|
|
|
|
#include <FEXCore/Config/Config.h>
|
|
#include <FEXCore/Core/Context.h>
|
|
#include <FEXCore/Core/CoreState.h>
|
|
#include <FEXCore/Core/CPUBackend.h>
|
|
#include <FEXCore/Debug/InternalThreadState.h>
|
|
#include <FEXCore/Utils/Allocator.h>
|
|
#include <FEXCore/Utils/LogManager.h>
|
|
|
|
#include <csetjmp>
|
|
#include <cstdint>
|
|
#include <errno.h>
|
|
#include <memory>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
#include <vector>
|
|
#include <utility>
|
|
|
|
void MsgHandler(LogMan::DebugLevels Level, char const *Message) {
|
|
const char *CharLevel{nullptr};
|
|
|
|
switch (Level) {
|
|
case LogMan::NONE:
|
|
CharLevel = "NONE";
|
|
break;
|
|
case LogMan::ASSERT:
|
|
CharLevel = "ASSERT";
|
|
break;
|
|
case LogMan::ERROR:
|
|
CharLevel = "ERROR";
|
|
break;
|
|
case LogMan::DEBUG:
|
|
CharLevel = "DEBUG";
|
|
break;
|
|
case LogMan::INFO:
|
|
CharLevel = "Info";
|
|
break;
|
|
default:
|
|
CharLevel = "???";
|
|
break;
|
|
}
|
|
fmt::print("[{}] {}\n", CharLevel, Message);
|
|
}
|
|
|
|
void AssertHandler(char const *Message) {
|
|
fmt::print("[ASSERT] {}\n", Message);
|
|
|
|
// make sure buffers are flushed
|
|
fflush(nullptr);
|
|
}
|
|
|
|
namespace {
|
|
static const std::vector<std::pair<const char*, FEXCore::Config::ConfigOption>> EnvConfigLookup = {{
|
|
#define OPT_BASE(type, group, enum, json, default) {"FEX_" #enum, FEXCore::Config::ConfigOption::CONFIG_##enum},
|
|
#include <FEXCore/Config/ConfigValues.inl>
|
|
}};
|
|
|
|
// Claims to be a local application config layer
|
|
class TestEnvLoader final : public FEXCore::Config::Layer {
|
|
public:
|
|
explicit TestEnvLoader(std::vector<std::pair<std::string_view, std::string_view>> _Env)
|
|
: FEXCore::Config::Layer(FEXCore::Config::LayerType::LAYER_LOCAL_APP)
|
|
, Env {std::move(_Env)} {
|
|
Load();
|
|
}
|
|
|
|
void Load() override {
|
|
std::unordered_map<std::string_view, std::string_view> EnvMap;
|
|
for (auto &Option : Env) {
|
|
std::string_view Key = Option.first;
|
|
std::string_view Value = Option.second;
|
|
|
|
#define ENVLOADER
|
|
#include <FEXCore/Config/ConfigOptions.inl>
|
|
|
|
EnvMap.insert_or_assign(Key, Value);
|
|
}
|
|
|
|
auto GetVar = [&](const std::string_view id) -> std::optional<std::string_view> {
|
|
const auto it = EnvMap.find(id);
|
|
if (it == EnvMap.end())
|
|
return std::nullopt;
|
|
|
|
return it->second;
|
|
};
|
|
|
|
for (auto &it : EnvConfigLookup) {
|
|
if (auto Value = GetVar(it.first); Value) {
|
|
Set(it.second, *Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
std::vector<std::pair<std::string_view, std::string_view>> Env;
|
|
};
|
|
}
|
|
|
|
int main(int argc, char **argv, char **const envp) {
|
|
LogMan::Throw::InstallHandler(AssertHandler);
|
|
LogMan::Msg::InstallHandler(MsgHandler);
|
|
FEXCore::Config::Initialize();
|
|
FEXCore::Config::AddLayer(std::make_unique<FEX::ArgLoader::ArgLoader>(argc, argv));
|
|
FEXCore::Config::AddLayer(FEXCore::Config::CreateEnvironmentLayer(envp));
|
|
FEXCore::Config::Load();
|
|
|
|
auto Args = FEX::ArgLoader::Get();
|
|
|
|
if (Args.size() < 2) {
|
|
LogMan::Msg::EFmt("Not enough arguments");
|
|
return -1;
|
|
}
|
|
|
|
FEX::HarnessHelper::HarnessCodeLoader Loader{Args[0], Args[1].c_str()};
|
|
|
|
// Adds in environment options from the test harness config
|
|
FEXCore::Config::AddLayer(std::make_unique<TestEnvLoader>(Loader.GetEnvironmentOptions()));
|
|
FEXCore::Config::ReloadMetaLayer();
|
|
|
|
FEXCore::Config::Set(FEXCore::Config::CONFIG_IS64BIT_MODE, Loader.Is64BitMode() ? "1" : "0");
|
|
|
|
FEX_CONFIG_OPT(Core, CORE);
|
|
|
|
std::unique_ptr<FEX::HLE::MemAllocator> Allocator;
|
|
|
|
if (!Loader.Is64BitMode()) {
|
|
// Setup our userspace allocator
|
|
uint32_t KernelVersion = FEX::HLE::SyscallHandler::CalculateHostKernelVersion();
|
|
if (KernelVersion >= FEX::HLE::SyscallHandler::KernelVersion(4, 17)) {
|
|
FEXCore::Allocator::SetupHooks();
|
|
}
|
|
|
|
if (KernelVersion < FEX::HLE::SyscallHandler::KernelVersion(4, 17)) {
|
|
Allocator = FEX::HLE::Create32BitAllocator();
|
|
}
|
|
else {
|
|
Allocator = FEX::HLE::CreatePassthroughAllocator();
|
|
}
|
|
}
|
|
|
|
auto SignalDelegation = std::make_unique<FEX::HLE::SignalDelegator>();
|
|
bool DidFault = false;
|
|
FEXCore::Core::CPUState State;
|
|
if (Core != FEXCore::Config::CONFIG_CUSTOM) {
|
|
jmp_buf LongJump{};
|
|
int LongJumpVal{};
|
|
|
|
SignalDelegation->RegisterFrontendHostSignalHandler(SIGSEGV, [&DidFault, &LongJump](FEXCore::Core::InternalThreadState *Thread, int Signal, void *info, void *ucontext) {
|
|
constexpr uint8_t HLT = 0xF4;
|
|
if (reinterpret_cast<uint8_t*>(Thread->CurrentFrame->State.rip)[0] != HLT) {
|
|
DidFault = false;
|
|
return false;
|
|
}
|
|
|
|
longjmp(LongJump, 1);
|
|
return false;
|
|
}, true);
|
|
|
|
// Run through FEX
|
|
FEXCore::Context::InitializeStaticTables(Loader.Is64BitMode() ? FEXCore::Context::MODE_64BIT : FEXCore::Context::MODE_32BIT);
|
|
|
|
auto CTX = FEXCore::Context::CreateNewContext();
|
|
|
|
FEXCore::Context::InitializeContext(CTX);
|
|
|
|
auto SyscallHandler = Loader.Is64BitMode() ? FEX::HLE::x64::CreateHandler(CTX, SignalDelegation.get())
|
|
: FEX::HLE::x32::CreateHandler(CTX, SignalDelegation.get(), std::move(Allocator));
|
|
|
|
auto Mapper = std::bind_front(&FEX::HLE::SyscallHandler::GuestMmap, SyscallHandler.get());
|
|
auto Unmapper = std::bind_front(&FEX::HLE::SyscallHandler::GuestMunmap, SyscallHandler.get());
|
|
|
|
if (!Loader.MapMemory(Mapper, Unmapper)) {
|
|
// failed to map
|
|
LogMan::Msg::EFmt("Failed to map %d-bit elf file.", Loader.Is64BitMode() ? 64 : 32);
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
FEXCore::Context::SetSignalDelegator(CTX, SignalDelegation.get());
|
|
FEXCore::Context::SetSyscallHandler(CTX, SyscallHandler.get());
|
|
bool Result1 = FEXCore::Context::InitCore(CTX, Loader.DefaultRIP(), Loader.GetStackPointer());
|
|
|
|
if (!Result1)
|
|
return 1;
|
|
|
|
LongJumpVal = setjmp(LongJump);
|
|
if (!LongJumpVal) {
|
|
FEXCore::Context::RunUntilExit(CTX);
|
|
}
|
|
|
|
// Just re-use compare state. It also checks against the expected values in config.
|
|
FEXCore::Context::GetCPUState(CTX, &State);
|
|
|
|
SyscallHandler.reset();
|
|
FEXCore::Context::DestroyContext(CTX);
|
|
FEXCore::Context::ShutdownStaticTables();
|
|
} else {
|
|
// Run as host
|
|
SignalDelegation->RegisterTLSState((FEXCore::Core::InternalThreadState*)UINTPTR_MAX);
|
|
if (!Loader.MapMemory(mmap, munmap)) {
|
|
// failed to map
|
|
LogMan::Msg::EFmt("Failed to map %d-bit elf file.", Loader.Is64BitMode() ? 64 : 32);
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
RunAsHost(SignalDelegation, Loader.DefaultRIP(), Loader.GetStackPointer(), &State);
|
|
}
|
|
|
|
bool Passed = !DidFault && Loader.CompareStates(&State, nullptr);
|
|
|
|
LogMan::Msg::IFmt("Faulted? {}", DidFault ? "Yes" : "No");
|
|
LogMan::Msg::IFmt("Passed? {}", Passed ? "Yes" : "No");
|
|
|
|
|
|
SignalDelegation.reset();
|
|
|
|
FEXCore::Config::Shutdown();
|
|
|
|
LogMan::Throw::UnInstallHandlers();
|
|
LogMan::Msg::UnInstallHandlers();
|
|
|
|
FEXCore::Allocator::ClearHooks();
|
|
|
|
return Passed ? 0 : -1;
|
|
}
|
|
|