Files
FEX-Emu--FEX/Source/Tests/TestHarnessRunner.cpp
T
Ryan Houdek ff1d51c7bd FEXCore: Fix-up edge case behaviour on faulting instructions
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.
2022-07-17 20:56:31 -07:00

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;
}