From 941caf31cefce5212a562425f71cda20fbfa6c9b Mon Sep 17 00:00:00 2001 From: lizzie Date: Sun, 30 Nov 2025 06:05:53 +0100 Subject: [PATCH] [port] NetBSD and improper ctor for SpinLock fixes (#3092) So when libc starts it has to start at an entry point located into crt0, now most OSes will do "enough" setup to allow mprotect() and mmap() to be called in static ctors (remember they're called BEFORE main) By some stupid miracle, NetBSD doesn't; this means that using those functions on NetBSD will result in spurious results The reason why is still unknown to me, but this is also combined with the fact that allocating a big chunk of memory for the JIT will make NetBSD refuse to mprotect()/mmap() it in low memory situations (even when space is available); so I take the same approach as with solaris Also I now make it so fastmem handlers are NOT registered for OSes that disabled fastmem, this is because they pollute sigsegv and makes debugging stupidier Signed-off-by: lizzie lizzie@eden-emu.dev Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3092 Reviewed-by: Caio Oliveira Reviewed-by: MaranBr Co-authored-by: lizzie Co-committed-by: lizzie --- externals/CMakeLists.txt | 2 +- src/core/arm/dynarmic/arm_dynarmic_32.cpp | 4 +-- src/core/arm/dynarmic/arm_dynarmic_64.cpp | 2 +- src/dynarmic/CMakeLists.txt | 7 +++- .../src/dynarmic/backend/x64/a32_emit_x64.cpp | 8 +++-- .../src/dynarmic/backend/x64/a64_emit_x64.cpp | 8 +++-- .../dynarmic/backend/x64/block_of_code.cpp | 28 +++++++++------ .../src/dynarmic/common/spin_lock_x64.cpp | 35 ++++++++++--------- src/video_core/macro/macro_jit_x64.cpp | 12 ++++++- 9 files changed, 67 insertions(+), 39 deletions(-) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 51980dfffe..d19feb0b52 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -27,7 +27,7 @@ set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL ON) # Xbyak (also used by Dynarmic, so needs to be added first) if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) - if (PLATFORM_SUN OR PLATFORM_OPENBSD) + if (PLATFORM_SUN OR PLATFORM_OPENBSD OR PLATFORM_NETBSD OR PLATFORM_DRAGONFLY) AddJsonPackage(xbyak_sun) else() AddJsonPackage(xbyak) diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index 2acadaf3ed..0a63035943 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -211,7 +211,7 @@ std::shared_ptr ArmDynarmic32::MakeJit(Common::PageTable* pa config.enable_cycle_counting = !m_uses_wall_clock; // Code cache size -#if defined(ARCHITECTURE_arm64) || defined(__sun__) +#if defined(ARCHITECTURE_arm64) || defined(__sun__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) config.code_cache_size = std::uint32_t(128_MiB); #else config.code_cache_size = std::uint32_t(512_MiB); @@ -295,7 +295,7 @@ std::shared_ptr ArmDynarmic32::MakeJit(Common::PageTable* pa // Curated optimizations case Settings::CpuAccuracy::Auto: config.unsafe_optimizations = true; -#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__sun__) || defined(__HAIKU__) || defined(__DragonFly__) +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__sun__) || defined(__HAIKU__) || defined(__DragonFly__) || defined(__NetBSD__) config.fastmem_pointer = std::nullopt; config.fastmem_exclusive_access = false; #endif diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index f1be21c6cd..1075bedd28 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -270,7 +270,7 @@ std::shared_ptr ArmDynarmic64::MakeJit(Common::PageTable* pa config.enable_cycle_counting = !m_uses_wall_clock; // Code cache size -#if defined(ARCHITECTURE_arm64) || defined(__sun__) +#if defined(ARCHITECTURE_arm64) || defined(__sun__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) config.code_cache_size = std::uint32_t(128_MiB); #else config.code_cache_size = std::uint32_t(512_MiB); diff --git a/src/dynarmic/CMakeLists.txt b/src/dynarmic/CMakeLists.txt index 5f9506273f..6328d4c90d 100644 --- a/src/dynarmic/CMakeLists.txt +++ b/src/dynarmic/CMakeLists.txt @@ -18,7 +18,12 @@ endif() # Dynarmic project options option(DYNARMIC_ENABLE_CPU_FEATURE_DETECTION "Turning this off causes dynarmic to assume the host CPU doesn't support anything later than SSE3" ON) -option(DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT "Enables support for systems that require W^X" ${PLATFORM_OPENBSD}) +if (PLATFORM_OPENBSD OR PLATFORM_DRAGONFLY OR PLATFORM_NETBSD) + set(REQUIRE_WX ON) +else() + set(REQUIRE_WX OFF) +endif() +option(DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT "Enables support for systems that require W^X" ${REQUIRE_WX}) option(DYNARMIC_IGNORE_ASSERTS "Ignore asserts" OFF) option(DYNARMIC_TESTS_USE_UNICORN "Enable fuzzing tests against unicorn" OFF) diff --git a/src/dynarmic/src/dynarmic/backend/x64/a32_emit_x64.cpp b/src/dynarmic/src/dynarmic/backend/x64/a32_emit_x64.cpp index 56dec9725f..6c8a479ec3 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/a32_emit_x64.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/a32_emit_x64.cpp @@ -87,9 +87,11 @@ A32EmitX64::A32EmitX64(BlockOfCode& code, A32::UserConfig conf, A32::Jit* jit_in code.PreludeComplete(); ClearFastDispatchTable(); - exception_handler.SetFastmemCallback([this](u64 rip_) { - return FastmemCallback(rip_); - }); + if (conf.fastmem_pointer.has_value()) { + exception_handler.SetFastmemCallback([this](u64 rip_) { + return FastmemCallback(rip_); + }); + } } A32EmitX64::~A32EmitX64() = default; diff --git a/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64.cpp b/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64.cpp index 7fd01daa29..e92aec04cf 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64.cpp @@ -61,9 +61,11 @@ A64EmitX64::A64EmitX64(BlockOfCode& code, A64::UserConfig conf, A64::Jit* jit_in code.PreludeComplete(); ClearFastDispatchTable(); - exception_handler.SetFastmemCallback([this](u64 rip_) { - return FastmemCallback(rip_); - }); + if (conf.fastmem_pointer.has_value()) { + exception_handler.SetFastmemCallback([this](u64 rip_) { + return FastmemCallback(rip_); + }); + } } A64EmitX64::~A64EmitX64() = default; diff --git a/src/dynarmic/src/dynarmic/backend/x64/block_of_code.cpp b/src/dynarmic/src/dynarmic/backend/x64/block_of_code.cpp index 8dfe84b37c..8bf3707674 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/block_of_code.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/block_of_code.cpp @@ -87,18 +87,24 @@ public: // Waste a page to store the size size += DYNARMIC_PAGE_SIZE; -# if defined(MAP_ANONYMOUS) - int mode = MAP_PRIVATE | MAP_ANONYMOUS; -# elif defined(MAP_ANON) - int mode = MAP_PRIVATE | MAP_ANON; -# else -# error "not supported" -# endif -# ifdef MAP_JIT + int mode = MAP_PRIVATE; +#if defined(MAP_ANONYMOUS) + mode |= MAP_ANONYMOUS; +#elif defined(MAP_ANON) + mode |= MAP_ANON; +#else +# error "not supported" +#endif +#ifdef MAP_JIT mode |= MAP_JIT; -# endif - - void* p = mmap(nullptr, size, PROT_READ | PROT_WRITE, mode, -1, 0); +#endif + int prot = PROT_READ | PROT_WRITE; +#ifdef PROT_MPROTECT + // https://man.netbsd.org/mprotect.2 specifies that an mprotect() that is LESS + // restrictive than the original mapping MUST fail + prot |= PROT_MPROTECT(PROT_READ) | PROT_MPROTECT(PROT_WRITE) | PROT_MPROTECT(PROT_EXEC); +#endif + void* p = mmap(nullptr, size, prot, mode, -1, 0); if (p == MAP_FAILED) { using Xbyak::Error; XBYAK_THROW(Xbyak::ERR_CANT_ALLOC); diff --git a/src/dynarmic/src/dynarmic/common/spin_lock_x64.cpp b/src/dynarmic/src/dynarmic/common/spin_lock_x64.cpp index da50179de9..f614c9774d 100644 --- a/src/dynarmic/src/dynarmic/common/spin_lock_x64.cpp +++ b/src/dynarmic/src/dynarmic/common/spin_lock_x64.cpp @@ -7,7 +7,7 @@ */ #include - +#include #include #include "dynarmic/backend/x64/abi.h" @@ -42,43 +42,46 @@ void EmitSpinLockUnlock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg } namespace { - struct SpinLockImpl { - void Initialize(); - + void Initialize() noexcept; + static void GlobalInitialize() noexcept; Xbyak::CodeGenerator code = Xbyak::CodeGenerator(4096, default_cg_mode); - - void (*lock)(volatile int*); - void (*unlock)(volatile int*); + void (*lock)(volatile int*) = nullptr; + void (*unlock)(volatile int*) = nullptr; }; std::once_flag flag; -SpinLockImpl impl; - -void SpinLockImpl::Initialize() { - const Xbyak::Reg64 ABI_PARAM1 = Backend::X64::HostLocToReg64(Backend::X64::ABI_PARAM1); +/// @brief Bear in mind that initializing the variable as-is on ctor time will trigger bugs +/// because some OSes do not prepare mprotect() properly at static ctor time +/// We can't really do anything about it, so just live with this fact +std::optional impl; +void SpinLockImpl::Initialize() noexcept { + Xbyak::Reg64 const ABI_PARAM1 = Backend::X64::HostLocToReg64(Backend::X64::ABI_PARAM1); code.align(); lock = code.getCurr(); EmitSpinLockLock(code, ABI_PARAM1, code.eax); code.ret(); - code.align(); unlock = code.getCurr(); EmitSpinLockUnlock(code, ABI_PARAM1, code.eax); code.ret(); } +void SpinLockImpl::GlobalInitialize() noexcept { + impl.emplace(); + impl->Initialize(); +} } // namespace void SpinLock::Lock() noexcept { - std::call_once(flag, &SpinLockImpl::Initialize, impl); - impl.lock(&storage); + std::call_once(flag, &SpinLockImpl::GlobalInitialize); + impl->lock(&storage); } void SpinLock::Unlock() noexcept { - std::call_once(flag, &SpinLockImpl::Initialize, impl); - impl.unlock(&storage); + std::call_once(flag, &SpinLockImpl::GlobalInitialize); + impl->unlock(&storage); } } // namespace Dynarmic diff --git a/src/video_core/macro/macro_jit_x64.cpp b/src/video_core/macro/macro_jit_x64.cpp index f8811d29c8..65935f6c62 100644 --- a/src/video_core/macro/macro_jit_x64.cpp +++ b/src/video_core/macro/macro_jit_x64.cpp @@ -44,10 +44,20 @@ std::bitset<32> PersistentCallerSavedRegs() { return PERSISTENT_REGISTERS & Common::X64::ABI_ALL_CALLER_SAVED; } +/// @brief Must enforce W^X constraints, as we yet don't havea global "NO_EXECUTE" support flag +/// the speed loss is minimal, and in fact may be negligible, however for your peace of mind +/// I simply included known OSes whom had W^X issues +#if defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) +static const auto default_cg_mode = Xbyak::DontSetProtectRWE; +#else +static const auto default_cg_mode = nullptr; //Allow RWE +#endif + class MacroJITx64Impl final : public Xbyak::CodeGenerator, public CachedMacro { public: explicit MacroJITx64Impl(Engines::Maxwell3D& maxwell3d_, const std::vector& code_) - : CodeGenerator{MAX_CODE_SIZE}, code{code_}, maxwell3d{maxwell3d_} { + : Xbyak::CodeGenerator(MAX_CODE_SIZE, default_cg_mode) + , code{code_}, maxwell3d{maxwell3d_} { Compile(); }