mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 22:00:16 +02:00
We actually never use this anymore, we instead always pass zero for both, and then rely on the thread inheritance model or setting the values manually. Now that we expose visibility of the InternalThreadState to the frontend they just access it directly. Just a smidge of cleanup, NFC.
427 lines
15 KiB
C++
427 lines
15 KiB
C++
// SPDX-License-Identifier: MIT
|
|
/*
|
|
$info$
|
|
tags: Bin|TestHarnessRunner
|
|
desc: Used to run Assembly tests
|
|
$end_info$
|
|
*/
|
|
|
|
#ifdef _WIN32
|
|
#include "DummyHandlers.h"
|
|
#include "ArchHelpers/WinContext.h"
|
|
#else
|
|
#include "LinuxSyscalls/LinuxAllocator.h"
|
|
#include "LinuxSyscalls/Syscalls.h"
|
|
#include "LinuxSyscalls/x32/Syscalls.h"
|
|
#include "LinuxSyscalls/x64/Syscalls.h"
|
|
#include "LinuxSyscalls/SignalDelegator.h"
|
|
#endif
|
|
|
|
#include "Common/HostFeatures.h"
|
|
#include "Common/Linux/SBRKAllocations.h"
|
|
#include "HarnessHelpers.h"
|
|
#include "TestHarnessRunner/HostRunner.h"
|
|
|
|
#include <FEXCore/Config/Config.h>
|
|
#include <FEXCore/Core/Context.h>
|
|
#include <FEXCore/Core/CoreState.h>
|
|
#include <FEXCore/Core/HostFeatures.h>
|
|
#include <FEXCore/Core/SignalDelegator.h>
|
|
#include <FEXCore/Debug/InternalThreadState.h>
|
|
#include <FEXCore/Utils/Allocator.h>
|
|
#include <FEXCore/Utils/ArchHelpers/Arm64.h>
|
|
#include <FEXCore/Utils/LogManager.h>
|
|
#include <FEXCore/fextl/memory.h>
|
|
#include <FEXCore/fextl/string.h>
|
|
#include <FEXCore/fextl/vector.h>
|
|
#include <FEXHeaderUtils/Filesystem.h>
|
|
|
|
#include <csetjmp>
|
|
#include <cstdint>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
#include <utility>
|
|
|
|
#ifdef ARCHITECTURE_x86_64
|
|
#include "Common/X86Features.h"
|
|
#endif
|
|
|
|
void MsgHandler(LogMan::DebugLevels Level, const char* Message) {
|
|
fextl::fmt::print("{} {}\n", LogMan::DebugLevelStr(Level), Message);
|
|
}
|
|
|
|
void AssertHandler(const char* Message) {
|
|
fextl::fmt::print("A {}\n", Message);
|
|
|
|
// make sure buffers are flushed
|
|
fflush(nullptr);
|
|
}
|
|
|
|
namespace {
|
|
static const fextl::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(fextl::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 {
|
|
fextl::unordered_map<std::string_view, std::string> EnvMap;
|
|
for (auto& Option : Env) {
|
|
std::string_view Key = Option.first;
|
|
std::string_view Value_View = Option.second;
|
|
std::optional<fextl::string> Value;
|
|
|
|
#define ENVLOADER
|
|
#include <FEXCore/Config/ConfigOptions.inl>
|
|
|
|
if (Value) {
|
|
EnvMap.insert_or_assign(Key, *Value);
|
|
} else {
|
|
EnvMap.insert_or_assign(Key, Value_View);
|
|
}
|
|
}
|
|
|
|
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:
|
|
fextl::vector<std::pair<std::string_view, std::string_view>> Env;
|
|
};
|
|
} // namespace
|
|
|
|
namespace LongJumpHandler {
|
|
static jmp_buf LongJump {};
|
|
static bool DidFault {};
|
|
|
|
#ifndef _WIN32
|
|
void RegisterLongJumpHandler(FEX::HLE::SignalDelegator* Handler) {
|
|
Handler->RegisterFrontendHostSignalHandler(
|
|
SIGSEGV,
|
|
[](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 = true;
|
|
return false;
|
|
}
|
|
|
|
longjmp(LongJumpHandler::LongJump, 1);
|
|
return false;
|
|
},
|
|
true);
|
|
}
|
|
#else
|
|
FEX::DummyHandlers::DummySignalDelegator* Handler;
|
|
|
|
static void LongJumpHandler() {
|
|
longjmp(LongJump, 1);
|
|
}
|
|
|
|
LONG WINAPI VectoredExceptionHandler(struct _EXCEPTION_POINTERS* ExceptionInfo) {
|
|
auto Thread = Handler->GetBackingTLSThread();
|
|
PCONTEXT Context;
|
|
Context = ExceptionInfo->ContextRecord;
|
|
|
|
switch (ExceptionInfo->ExceptionRecord->ExceptionCode) {
|
|
case STATUS_DATATYPE_MISALIGNMENT: {
|
|
const auto PC = FEX::ArchHelpers::Context::GetPc(Context);
|
|
if (!Thread->CTX->IsAddressInCodeBuffer(Thread, PC)) {
|
|
// Wasn't a sigbus in JIT code
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
const auto Result = FEXCore::ArchHelpers::Arm64::HandleUnalignedAccess(true, PC, FEX::ArchHelpers::Context::GetArmGPRs(Context));
|
|
FEX::ArchHelpers::Context::SetPc(Context, PC + Result.value_or(0));
|
|
return Result ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
case STATUS_ACCESS_VIOLATION: {
|
|
constexpr uint8_t HLT = 0xF4;
|
|
if (reinterpret_cast<uint8_t*>(Thread->CurrentFrame->State.rip)[0] != HLT) {
|
|
DidFault = true;
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
FEX::ArchHelpers::Context::SetPc(Context, reinterpret_cast<uint64_t>(LongJumpHandler));
|
|
return EXCEPTION_CONTINUE_EXECUTION;
|
|
}
|
|
default: break;
|
|
}
|
|
|
|
printf("!Fault!\n");
|
|
printf("\tExceptionCode: 0x%lx\n", ExceptionInfo->ExceptionRecord->ExceptionCode);
|
|
printf("\tExceptionFlags: 0x%lx\n", ExceptionInfo->ExceptionRecord->ExceptionFlags);
|
|
printf("\tExceptionRecord: 0x%p\n", ExceptionInfo->ExceptionRecord->ExceptionRecord);
|
|
printf("\tExceptionAddress: 0x%p\n", ExceptionInfo->ExceptionRecord->ExceptionAddress);
|
|
printf("\tNumberParameters: 0x%lx\n", ExceptionInfo->ExceptionRecord->NumberParameters);
|
|
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
void RegisterLongJumpHandler(FEX::DummyHandlers::DummySignalDelegator* Handler) {
|
|
// Install VEH handler.
|
|
AddVectoredExceptionHandler(0, VectoredExceptionHandler);
|
|
|
|
LongJumpHandler::Handler = Handler;
|
|
}
|
|
#endif
|
|
} // namespace LongJumpHandler
|
|
|
|
int main(int argc, char** argv, char** const envp) {
|
|
#ifndef _WIN32
|
|
auto SBRKPointer = FEX::SBRKAllocations::DisableSBRKAllocations();
|
|
#endif
|
|
FEXCore::Allocator::GLIBCScopedFault GLIBFaultScope;
|
|
LogMan::Throw::InstallHandler(AssertHandler);
|
|
LogMan::Msg::InstallHandler(MsgHandler);
|
|
|
|
FEX::Config::InitializeConfigs(FEX::Config::PortableInformation {});
|
|
FEXCore::Config::Initialize();
|
|
FEXCore::Config::AddLayer(FEX::Config::CreateEnvironmentLayer(envp));
|
|
FEXCore::Config::Load();
|
|
|
|
if (argc < 3) {
|
|
LogMan::Msg::EFmt("Not enough arguments");
|
|
return -1;
|
|
}
|
|
|
|
auto Filename = argv[1];
|
|
auto ConfigFile = argv[2];
|
|
|
|
if (!FHU::Filesystem::Exists(Filename)) {
|
|
LogMan::Msg::EFmt("File {} does not exist", Filename);
|
|
return -1;
|
|
}
|
|
|
|
if (!FHU::Filesystem::Exists(ConfigFile)) {
|
|
LogMan::Msg::EFmt("File {} does not exist", ConfigFile);
|
|
return -1;
|
|
}
|
|
|
|
FEX::HarnessHelper::HarnessCodeLoader Loader {Filename, ConfigFile};
|
|
|
|
// Adds in environment options from the test harness config
|
|
FEXCore::Config::AddLayer(fextl::make_unique<TestEnvLoader>(Loader.GetEnvironmentOptions()));
|
|
FEXCore::Config::ReloadMetaLayer();
|
|
|
|
FEXCore::Config::Set(FEXCore::Config::CONFIG_IS64BIT_MODE, Loader.Is64BitMode() ? "1" : "0");
|
|
#ifdef VIXL_SIMULATOR
|
|
// If running under the vixl simulator, ensure that indirect runtime calls are enabled.
|
|
FEXCore::Config::Set(FEXCore::Config::CONFIG_DISABLE_VIXL_INDIRECT_RUNTIME_CALLS, "0");
|
|
#endif
|
|
|
|
#ifndef _WIN32
|
|
fextl::unique_ptr<FEX::HLE::MemAllocator> Allocator;
|
|
|
|
if (!Loader.Is64BitMode()) {
|
|
// Setup our userspace allocator
|
|
const auto PageSize = sysconf(_SC_PAGESIZE);
|
|
FEXCore::Allocator::SetupHooks(PageSize > 0 ? PageSize : FEXCore::Utils::FEX_PAGE_SIZE);
|
|
Allocator = FEX::HLE::CreatePassthroughAllocator();
|
|
}
|
|
#endif
|
|
|
|
bool SupportsAVX = false;
|
|
FEXCore::Core::CPUState State;
|
|
|
|
auto HostFeatures = FEX::FetchHostFeatures();
|
|
auto CTX = FEXCore::Context::Context::CreateNewContext(HostFeatures);
|
|
|
|
#ifndef _WIN32
|
|
auto SignalDelegation = FEX::HLE::CreateSignalDelegator(CTX.get(), {}, HostFeatures.SupportsAVX, HostFeatures.SupportsSVE256);
|
|
#else
|
|
// Enable exit on HLT while Wine's longjump is broken.
|
|
//
|
|
// Once they fix longjump, we can remove this.
|
|
CTX->EnableExitOnHLT();
|
|
auto SignalDelegation = FEX::WindowsHandlers::CreateSignalDelegator();
|
|
#endif
|
|
|
|
// Skip any tests that the host doesn't support features for
|
|
SupportsAVX = HostFeatures.SupportsAVX;
|
|
|
|
bool TestUnsupported = (!SupportsAVX && Loader.RequiresAVX()) || (!HostFeatures.SupportsRAND && Loader.RequiresRAND()) ||
|
|
(!HostFeatures.SupportsSHA && Loader.RequiresSHA()) || (!HostFeatures.SupportsCLZERO && Loader.RequiresCLZERO()) ||
|
|
(!HostFeatures.SupportsAES256 && Loader.RequiresAES256()) || (!HostFeatures.SupportsAFP && Loader.RequiresAFP());
|
|
|
|
|
|
bool IsHostRunner = false;
|
|
#if !defined(VIXL_SIMULATOR) && defined(ARCHITECTURE_x86_64)
|
|
IsHostRunner = true;
|
|
///< Features that are only unsupported when running using the HostRunner and the CI machine doesn't support the feature getting tested.
|
|
FEX::X86::Features Feature {};
|
|
const bool Supports3DNow = Feature.Feat_3dnow;
|
|
const bool SupportsSSE4A = Feature.Feat_sse4a;
|
|
const bool SupportsBMI1 = Feature.Feat_bmi1;
|
|
const bool SupportsBMI2 = Feature.Feat_bmi2;
|
|
const bool SupportsCLWB = Feature.Feat_clwb;
|
|
const bool SupportsSSSE3 = Feature.Feat_ssse3;
|
|
const bool SupportsSSE4_1 = Feature.Feat_sse4_1;
|
|
const bool SupportsSSE4_2 = Feature.Feat_sse4_2;
|
|
const bool SupportsAES = Feature.Feat_aes;
|
|
const bool SupportsPCLMUL = Feature.Feat_pclmulqdq;
|
|
const bool SupportsMOVBE = Feature.Feat_movbe;
|
|
const bool SupportsADX = Feature.Feat_adx;
|
|
const bool SupportsXSAVE = Feature.Feat_xsave;
|
|
const bool SupportsRDPID = Feature.Feat_rdpid;
|
|
const bool SupportsCLFLOPT = Feature.Feat_clflopt;
|
|
const bool SupportsFSGSBase = Feature.Feat_fsgsbase;
|
|
|
|
TestUnsupported |=
|
|
(!Supports3DNow && Loader.Requires3DNow()) || (!SupportsSSE4A && Loader.RequiresSSE4A()) || (!SupportsBMI1 && Loader.RequiresBMI1()) ||
|
|
(!SupportsBMI2 && Loader.RequiresBMI2()) || (!SupportsCLWB && Loader.RequiresCLWB()) || (!SupportsSSSE3 && Loader.RequiresSSSE3()) ||
|
|
(!SupportsSSE4_1 && Loader.RequiresSSE4_1()) || (!SupportsSSE4_2 && Loader.RequiresSSE4_2()) ||
|
|
(!SupportsAES && Loader.RequiresAES()) || (!SupportsPCLMUL && Loader.RequiresPCLMUL()) || (!SupportsMOVBE && Loader.RequiresMOVBE()) ||
|
|
(!SupportsADX && Loader.RequiresADX()) || (!SupportsXSAVE && Loader.RequiresXSAVE()) || (!SupportsRDPID && Loader.RequiresRDPID()) ||
|
|
(!SupportsCLFLOPT && Loader.RequiresCLFLOPT()) || (!SupportsFSGSBase && Loader.RequiresFSGSBase()) || Loader.RequiresEMMI();
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
TestUnsupported |= Loader.RequiresLinux();
|
|
#endif
|
|
|
|
if (TestUnsupported) {
|
|
return 0;
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
auto SyscallHandler = Loader.Is64BitMode() ? FEX::HLE::x64::CreateHandler(CTX.get(), SignalDelegation.get(), nullptr) :
|
|
FEX::HLE::x32::CreateHandler(CTX.get(), SignalDelegation.get(), nullptr, std::move(Allocator));
|
|
|
|
auto DoMmap = [&](uint64_t Address, size_t Size) -> void* {
|
|
void* Result = SyscallHandler->GuestMmap(nullptr, (void*)Address, Size, PROT_READ | PROT_WRITE | PROT_EXEC,
|
|
MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0);
|
|
LOGMAN_THROW_A_FMT(Result == reinterpret_cast<void*>(Address), "Map Memory mmap failed");
|
|
return Result;
|
|
};
|
|
|
|
#else
|
|
auto SyscallHandler = FEX::WindowsHandlers::CreateSyscallHandler();
|
|
|
|
auto DoMMap = [](uint64_t Address, size_t Size) -> void* {
|
|
void* Result = FEXCore::Allocator::VirtualAlloc(reinterpret_cast<void*>(Address), Size, true);
|
|
LOGMAN_THROW_A_FMT(Result == reinterpret_cast<void*>(Address), "Map Memory mmap failed");
|
|
return Result;
|
|
};
|
|
#endif
|
|
|
|
CTX->SetSignalDelegator(SignalDelegation.get());
|
|
CTX->SetSyscallHandler(SyscallHandler.get());
|
|
|
|
if (!CTX->InitCore()) {
|
|
return 1;
|
|
}
|
|
|
|
if (!IsHostRunner) {
|
|
LongJumpHandler::RegisterLongJumpHandler(SignalDelegation.get());
|
|
|
|
// Run through FEX
|
|
if (!Loader.MapMemory(DoMmap)) {
|
|
// failed to map
|
|
LogMan::Msg::EFmt("Failed to map {}-bit elf file.", Loader.Is64BitMode() ? 64 : 32);
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
auto ParentThread = SyscallHandler->TM.CreateThread();
|
|
ParentThread->Thread->CurrentFrame->State.rip = Loader.DefaultRIP();
|
|
SyscallHandler->TM.TrackThread(ParentThread);
|
|
SignalDelegation->RegisterTLSState(ParentThread);
|
|
|
|
if (!ParentThread) {
|
|
return 1;
|
|
}
|
|
|
|
int LongJumpVal = setjmp(LongJumpHandler::LongJump);
|
|
if (!LongJumpVal) {
|
|
CTX->ExecuteThread(ParentThread->Thread);
|
|
}
|
|
|
|
// Just re-use compare state. It also checks against the expected values in config.
|
|
memcpy(&State, &ParentThread->Thread->CurrentFrame->State, sizeof(State));
|
|
|
|
__uint128_t XMM_Low[FEXCore::Core::CPUState::NUM_XMMS];
|
|
if (SupportsAVX) {
|
|
///< Reconstruct the XMM registers even if they are in split view, then remerge them.
|
|
__uint128_t YMM_High[FEXCore::Core::CPUState::NUM_XMMS];
|
|
CTX->ReconstructXMMRegisters(ParentThread->Thread, XMM_Low, YMM_High);
|
|
for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_XMMS; ++i) {
|
|
memcpy(&State.xmm.avx.data[i][0], &XMM_Low[i], sizeof(__uint128_t));
|
|
memcpy(&State.xmm.avx.data[i][2], &YMM_High[i], sizeof(__uint128_t));
|
|
}
|
|
} else {
|
|
CTX->ReconstructXMMRegisters(ParentThread->Thread, reinterpret_cast<__uint128_t*>(State.xmm.sse.data), nullptr);
|
|
}
|
|
|
|
SignalDelegation->UninstallTLSState(ParentThread);
|
|
FEX::HLE::_SyscallHandler->TM.DestroyThread(ParentThread, true);
|
|
}
|
|
#ifndef _WIN32
|
|
else {
|
|
// Run as host
|
|
SupportsAVX = true;
|
|
auto ParentThread = SyscallHandler->TM.CreateThread();
|
|
SyscallHandler->TM.TrackThread(ParentThread);
|
|
SignalDelegation->RegisterTLSState(ParentThread);
|
|
|
|
auto DoMmap = [&](uint64_t Address, size_t Size) -> void* {
|
|
void* Result = mmap((void*)Address, Size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0);
|
|
LOGMAN_THROW_A_FMT(Result == reinterpret_cast<void*>(Address), "Map Memory mmap failed");
|
|
return Result;
|
|
};
|
|
if (!Loader.MapMemory(DoMmap)) {
|
|
// failed to map
|
|
LogMan::Msg::EFmt("Failed to map {}-bit elf file.", Loader.Is64BitMode() ? 64 : 32);
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
RunAsHost(SignalDelegation.get(), Loader.DefaultRIP(), &State);
|
|
SignalDelegation->UninstallTLSState(ParentThread);
|
|
FEX::HLE::_SyscallHandler->TM.DestroyThread(ParentThread, true);
|
|
}
|
|
#endif
|
|
|
|
SyscallHandler.reset();
|
|
|
|
bool Passed = !LongJumpHandler::DidFault && Loader.CompareStates(&State, nullptr, SupportsAVX);
|
|
|
|
LogMan::Msg::IFmt("Faulted? {}", LongJumpHandler::DidFault ? "Yes" : "No");
|
|
LogMan::Msg::IFmt("Passed? {}", Passed ? "Yes" : "No");
|
|
|
|
|
|
SignalDelegation.reset();
|
|
|
|
FEXCore::Config::Shutdown();
|
|
|
|
LogMan::Throw::UnInstallHandler();
|
|
LogMan::Msg::UnInstallHandler();
|
|
|
|
#ifndef _WIN32
|
|
FEXCore::Allocator::ClearHooks();
|
|
|
|
FEX::SBRKAllocations::ReenableSBRKAllocations(SBRKPointer);
|
|
#endif
|
|
|
|
return Passed ? 0 : -1;
|
|
}
|