diff --git a/src/common/crash_handler.cpp b/src/common/crash_handler.cpp index b8dd4540d..1dbc98056 100644 --- a/src/common/crash_handler.cpp +++ b/src/common/crash_handler.cpp @@ -85,6 +85,7 @@ static bool WriteMinidump(HMODULE hDbgHelp, HANDLE hFile, HANDLE hProcess, DWORD static std::wstring s_write_directory; static DynamicLibrary s_dbghelp_module; +static CrashHandler::CleanupHandler s_cleanup_handler; static bool s_in_crash_handler = false; static void GenerateCrashFilename(wchar_t* buf, size_t len, const wchar_t* prefix, const wchar_t* extension) @@ -99,8 +100,6 @@ static void GenerateCrashFilename(wchar_t* buf, size_t len, const wchar_t* prefi static void WriteMinidumpAndCallstack(PEXCEPTION_POINTERS exi) { - s_in_crash_handler = true; - wchar_t filename[1024] = {}; GenerateCrashFilename(filename, std::size(filename), s_write_directory.empty() ? nullptr : s_write_directory.c_str(), L"txt"); @@ -148,7 +147,13 @@ static LONG NTAPI ExceptionHandler(PEXCEPTION_POINTERS exi) { // if the debugger is attached, or we're recursively crashing, let it take care of it. if (!s_in_crash_handler) + { + s_in_crash_handler = true; + if (s_cleanup_handler) + s_cleanup_handler(); + WriteMinidumpAndCallstack(exi); + } // returning EXCEPTION_CONTINUE_SEARCH makes sense, except for the fact that it seems to leave zombie processes // around. instead, force ourselves to terminate. @@ -156,7 +161,7 @@ static LONG NTAPI ExceptionHandler(PEXCEPTION_POINTERS exi) return EXCEPTION_CONTINUE_SEARCH; } -bool CrashHandler::Install() +bool CrashHandler::Install(CleanupHandler cleanup_handler) { // load dbghelp at install/startup, that way we're not LoadLibrary()'ing after a crash // .. because that probably wouldn't go down well. @@ -165,6 +170,7 @@ bool CrashHandler::Install() s_dbghelp_module.Adopt(mod); SetUnhandledExceptionFilter(ExceptionHandler); + s_cleanup_handler = cleanup_handler; return true; } @@ -208,6 +214,7 @@ static void LogCallstack(int signal, const void* exception_pc); static std::recursive_mutex s_crash_mutex; static bool s_in_signal_handler = false; +static CleanupHandler s_cleanup_handler; static backtrace_state* s_backtrace_state = nullptr; } // namespace CrashHandler @@ -304,6 +311,9 @@ void CrashHandler::CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx) { s_in_signal_handler = true; + if (s_cleanup_handler) + s_cleanup_handler(); + #if defined(__APPLE__) && defined(__x86_64__) void* const exception_pc = reinterpret_cast(static_cast(ctx)->uc_mcontext->__ss.__rip); #elif defined(__FreeBSD__) && defined(__x86_64__) @@ -327,7 +337,7 @@ void CrashHandler::CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx) std::abort(); } -bool CrashHandler::Install() +bool CrashHandler::Install(CleanupHandler cleanup_handler) { const std::string progpath = FileSystem::GetProgramPath(); s_backtrace_state = backtrace_create_state(progpath.empty() ? nullptr : progpath.c_str(), 0, nullptr, nullptr); @@ -344,6 +354,7 @@ bool CrashHandler::Install() if (sigaction(SIGSEGV, &sa, nullptr) != 0) return false; + s_cleanup_handler = cleanup_handler; return true; } @@ -358,7 +369,7 @@ void CrashHandler::WriteDumpForCaller() #else -bool CrashHandler::Install() +bool CrashHandler::Install(CleanupHandler cleanup_handler) { return false; } diff --git a/src/common/crash_handler.h b/src/common/crash_handler.h index 0ca2a3a55..3b632d69b 100644 --- a/src/common/crash_handler.h +++ b/src/common/crash_handler.h @@ -1,7 +1,8 @@ // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) -#include "types.h" +#pragma once + #include #ifndef _WIN32 @@ -9,13 +10,21 @@ #endif namespace CrashHandler { -bool Install(); + +/// Adds a callback to run just before the crash handler exits. +/// It's not guaranteed that this handler will actually run, because the process state could be very messed up by this +/// point. It's mainly a thing so that we can free up the shared memory object if there was one created. +using CleanupHandler = void(*)(); + +bool Install(CleanupHandler cleanup_handler); void SetWriteDirectory(std::string_view dump_directory); void WriteDumpForCaller(); #ifndef _WIN32 + // Allow crash handler to be invoked from a signal. void CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx); + #endif } // namespace CrashHandler diff --git a/src/common/memmap.cpp b/src/common/memmap.cpp index d6e63699c..9929d526e 100644 --- a/src/common/memmap.cpp +++ b/src/common/memmap.cpp @@ -66,9 +66,10 @@ std::string MemMap::GetFileMappingName(const char* prefix) void* MemMap::CreateSharedMemory(const char* name, size_t size, Error* error) { + const std::wstring mapping_name = name ? StringUtil::UTF8StringToWideString(name) : std::wstring(); const HANDLE mapping = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, static_cast(size >> 32), - static_cast(size), StringUtil::UTF8StringToWideString(name).c_str()); + static_cast(size), mapping_name.empty() ? nullptr : mapping_name.c_str()); if (!mapping) Error::SetWin32(error, "CreateFileMappingW() failed: ", GetLastError()); @@ -80,6 +81,11 @@ void MemMap::DestroySharedMemory(void* ptr) CloseHandle(static_cast(ptr)); } +void MemMap::DeleteSharedMemory(const char* name) +{ + // Automatically freed on close. +} + void* MemMap::MapSharedMemory(void* handle, size_t offset, void* baseaddr, size_t size, PageProtect mode) { void* ret = MapViewOfFileEx(static_cast(handle), FILE_MAP_READ | FILE_MAP_WRITE, @@ -374,6 +380,10 @@ void MemMap::DestroySharedMemory(void* ptr) mach_port_deallocate(mach_task_self(), static_cast(reinterpret_cast(ptr))); } +void MemMap::DeleteSharedMemory(const char* name) +{ +} + void* MemMap::MapSharedMemory(void* handle, size_t offset, void* baseaddr, size_t size, PageProtect mode) { mach_vm_address_t ptr = reinterpret_cast(baseaddr); @@ -617,15 +627,25 @@ std::string MemMap::GetFileMappingName(const char* prefix) void* MemMap::CreateSharedMemory(const char* name, size_t size, Error* error) { + const bool is_anonymous = (!name || *name == 0); +#if defined(__linux__) || defined(__FreeBSD__) + const int fd = is_anonymous ? memfd_create("", 0) : shm_open(name, O_CREAT | O_EXCL | O_RDWR, 0600); + if (fd < 0) + { + Error::SetErrno(error, is_anonymous ? "memfd_create() failed: " : "shm_open() failed: ", errno); + return nullptr; + } +#else const int fd = shm_open(name, O_CREAT | O_EXCL | O_RDWR, 0600); if (fd < 0) { - Error::SetErrno(error, "shm_open failed: ", errno); + Error::SetErrno(error, "shm_open() failed: ", errno); return nullptr; } // we're not going to be opening this mapping in other processes, so remove the file shm_unlink(name); +#endif // use fallocate() to ensure we don't SIGBUS later on. #ifdef __linux__ @@ -651,6 +671,11 @@ void MemMap::DestroySharedMemory(void* ptr) close(static_cast(reinterpret_cast(ptr))); } +void MemMap::DeleteSharedMemory(const char* name) +{ + shm_unlink(name); +} + void* MemMap::MapSharedMemory(void* handle, size_t offset, void* baseaddr, size_t size, PageProtect mode) { const int flags = (baseaddr != nullptr) ? (MAP_SHARED | MAP_FIXED) : MAP_SHARED; diff --git a/src/common/memmap.h b/src/common/memmap.h index aa65cde93..f7ca87797 100644 --- a/src/common/memmap.h +++ b/src/common/memmap.h @@ -53,6 +53,7 @@ class Error; namespace MemMap { std::string GetFileMappingName(const char* prefix); void* CreateSharedMemory(const char* name, size_t size, Error* error); +void DeleteSharedMemory(const char* name); void DestroySharedMemory(void* ptr); void* MapSharedMemory(void* handle, size_t offset, void* baseaddr, size_t size, PageProtect mode); void UnmapSharedMemory(void* baseaddr, size_t size); diff --git a/src/core/bus.cpp b/src/core/bus.cpp index 3299eb532..f470b44cd 100644 --- a/src/core/bus.cpp +++ b/src/core/bus.cpp @@ -118,6 +118,7 @@ union RAM_SIZE_REG } // namespace static void* s_shmem_handle = nullptr; +static std::string s_shmem_name; std::bitset g_ram_code_bits{}; u8* g_ram = nullptr; @@ -153,6 +154,8 @@ static u8** s_fastmem_lut = nullptr; static bool s_kernel_initialize_hook_run = false; +static bool AllocateMemoryMap(Error* error); +static void ReleaseMemoryMap(); static void SetRAMSize(bool enable_8mb_ram); static std::tuple CalculateMemoryTiming(MEMDELAY mem_delay, COMDELAY common_delay); @@ -194,10 +197,12 @@ static constexpr size_t TOTAL_SIZE = LUT_OFFSET + LUT_SIZE; #define FIXUP_WORD_WRITE_VALUE(size, offset, value) \ ((size == MemoryAccessSize::Word) ? (value) : ((value) << (((offset) & 3u) * 8))) -bool Bus::AllocateMemory(Error* error) +bool Bus::AllocateMemoryMap(Error* error) { - s_shmem_handle = - MemMap::CreateSharedMemory(MemMap::GetFileMappingName("duckstation").c_str(), MemoryMap::TOTAL_SIZE, error); + // This executes super early in process startup, therefore export_shared_memory will always be false. + if (g_settings.export_shared_memory) + s_shmem_name = MemMap::GetFileMappingName("duckstation"); + s_shmem_handle = MemMap::CreateSharedMemory(s_shmem_name.c_str(), MemoryMap::TOTAL_SIZE, error); if (!s_shmem_handle) { #ifndef __linux__ @@ -216,7 +221,7 @@ bool Bus::AllocateMemory(Error* error) if (!g_ram || !g_unprotected_ram) { Error::SetStringView(error, "Failed to map memory for RAM"); - ReleaseMemory(); + ReleaseMemoryMap(); return false; } @@ -227,7 +232,7 @@ bool Bus::AllocateMemory(Error* error) if (!g_bios) { Error::SetStringView(error, "Failed to map memory for BIOS"); - ReleaseMemory(); + ReleaseMemoryMap(); return false; } @@ -238,7 +243,7 @@ bool Bus::AllocateMemory(Error* error) if (!g_memory_handlers) { Error::SetStringView(error, "Failed to map memory for LUTs"); - ReleaseMemory(); + ReleaseMemoryMap(); return false; } @@ -246,17 +251,6 @@ bool Bus::AllocateMemory(Error* error) g_memory_handlers_isc = g_memory_handlers + MEMORY_LUT_SLOTS; SetHandlers(); -#ifdef ENABLE_MMAP_FASTMEM - if (!s_fastmem_arena.Create(FASTMEM_ARENA_SIZE)) - { - Error::SetStringView(error, "Failed to create fastmem arena"); - ReleaseMemory(); - return false; - } - - INFO_LOG("Fastmem base: {}", static_cast(s_fastmem_arena.BasePointer())); -#endif - #ifndef __ANDROID__ Exports::RAM = reinterpret_cast(g_unprotected_ram); #endif @@ -264,7 +258,7 @@ bool Bus::AllocateMemory(Error* error) return true; } -void Bus::ReleaseMemory() +void Bus::ReleaseMemoryMap() { #ifndef __ANDROID__ Exports::RAM = 0; @@ -272,14 +266,6 @@ void Bus::ReleaseMemory() Exports::RAM_MASK = 0; #endif -#ifdef ENABLE_MMAP_FASTMEM - DebugAssert(s_fastmem_ram_views.empty()); - s_fastmem_arena.Destroy(); -#endif - - std::free(s_fastmem_lut); - s_fastmem_lut = nullptr; - g_memory_handlers_isc = nullptr; if (g_memory_handlers) { @@ -309,9 +295,88 @@ void Bus::ReleaseMemory() { MemMap::DestroySharedMemory(s_shmem_handle); s_shmem_handle = nullptr; + + if (!s_shmem_name.empty()) + { + MemMap::DeleteSharedMemory(s_shmem_name.c_str()); + s_shmem_name = {}; + } } } +bool Bus::AllocateMemory(Error* error) +{ + if (!AllocateMemoryMap(error)) + return false; + +#ifdef ENABLE_MMAP_FASTMEM + if (!s_fastmem_arena.Create(FASTMEM_ARENA_SIZE)) + { + Error::SetStringView(error, "Failed to create fastmem arena"); + ReleaseMemory(); + return false; + } + + INFO_LOG("Fastmem base: {}", static_cast(s_fastmem_arena.BasePointer())); +#endif + + return true; +} + +void Bus::ReleaseMemory() +{ +#ifdef ENABLE_MMAP_FASTMEM + DebugAssert(s_fastmem_ram_views.empty()); + s_fastmem_arena.Destroy(); +#endif + + std::free(s_fastmem_lut); + s_fastmem_lut = nullptr; + + ReleaseMemoryMap(); +} + +bool Bus::ReallocateMemoryMap(Error* error) +{ + // Need to back up RAM+BIOS. + DynamicHeapArray ram_backup; + DynamicHeapArray bios_backup; + + if (System::IsValid()) + { + CPU::CodeCache::InvalidateAllRAMBlocks(); + UpdateFastmemViews(CPUFastmemMode::Disabled); + + ram_backup.resize(RAM_8MB_SIZE); + std::memcpy(ram_backup.data(), g_unprotected_ram, RAM_8MB_SIZE); + bios_backup.resize(BIOS_SIZE); + std::memcpy(bios_backup.data(), g_bios, BIOS_SIZE); + } + + ReleaseMemoryMap(); + if (!AllocateMemoryMap(error)) [[unlikely]] + return false; + + if (System::IsValid()) + { + UpdateMappedRAMSize(); + std::memcpy(g_unprotected_ram, ram_backup.data(), RAM_8MB_SIZE); + std::memcpy(g_bios, bios_backup.data(), BIOS_SIZE); + UpdateFastmemViews(g_settings.cpu_fastmem_mode); + } + + return true; +} + +void Bus::CleanupMemoryMap() +{ +#if !defined(_WIN32) && !defined(__ANDROID__) + // This is only needed on Linux. + if (!s_shmem_name.empty()) + MemMap::DeleteSharedMemory(s_shmem_name.c_str()); +#endif +} + bool Bus::Initialize() { SetRAMSize(g_settings.enable_8mb_ram); @@ -337,12 +402,6 @@ void Bus::Shutdown() g_ram_mask = 0; g_ram_size = 0; - -#ifndef __ANDROID__ - Exports::RAM = 0; - Exports::RAM_SIZE = 0; - Exports::RAM_MASK = 0; -#endif } void Bus::Reset() diff --git a/src/core/bus.h b/src/core/bus.h index af5013134..4c7aa98c1 100644 --- a/src/core/bus.h +++ b/src/core/bus.h @@ -116,6 +116,14 @@ static constexpr size_t FASTMEM_ARENA_SIZE = UINT64_C(0x100000000); bool AllocateMemory(Error* error); void ReleaseMemory(); +/// Frees and re-allocates the memory map for the process. +/// This should be called when shared memory exports are enabled. +bool ReallocateMemoryMap(Error* error); + +/// Cleans up/deletes the shared memory object for this process. +/// Should be called when the process crashes, to avoid leaking. +void CleanupMemoryMap(); + bool Initialize(); void Shutdown(); void Reset(); diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 85ca2c411..805a04c66 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -337,6 +337,7 @@ void Settings::Load(SettingsInterface& si) audio_output_muted = si.GetBoolValue("Audio", "OutputMuted", false); use_old_mdec_routines = si.GetBoolValue("Hacks", "UseOldMDECRoutines", false); + export_shared_memory = si.GetBoolValue("Hacks", "ExportSharedMemory", false); pcdrv_enable = si.GetBoolValue("PCDrv", "Enabled", false); pcdrv_enable_writes = si.GetBoolValue("PCDrv", "EnableWrites", false); pcdrv_root = si.GetStringValue("PCDrv", "Root"); @@ -597,6 +598,7 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const si.SetBoolValue("Audio", "OutputMuted", audio_output_muted); si.SetBoolValue("Hacks", "UseOldMDECRoutines", use_old_mdec_routines); + si.SetBoolValue("Hacks", "ExportSharedMemory", export_shared_memory); if (!ignore_base) { diff --git a/src/core/settings.h b/src/core/settings.h index c939549c8..01b163c81 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -199,6 +199,7 @@ struct Settings bool use_old_mdec_routines : 1 = false; bool pcdrv_enable : 1 = false; + bool export_shared_memory : 1 = false; // timing hacks section TickCount dma_max_slice_ticks = DEFAULT_DMA_MAX_SLICE_TICKS; diff --git a/src/core/system.cpp b/src/core/system.cpp index 0f822e74e..e824be28d 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -128,6 +128,9 @@ static bool SaveUndoLoadState(); static void WarnAboutUnsafeSettings(); static void LogUnsafeSettingsToConsole(const SmallStringBase& messages); +/// Checks for settings changes, std::move() the old settings away for comparing beforehand. +static void CheckForSettingsChanges(const Settings& old_settings); + /// Throttles the system, i.e. sleeps until it's time to execute the next frame. static void Throttle(Common::Timer::Value current_time); static void UpdatePerformanceCounters(); @@ -436,6 +439,13 @@ bool System::Internal::CPUThreadInitialize(Error* error) // This will call back to Host::LoadSettings() -> ReloadSources(). LoadSettings(false); + // Yuckity yuck. We have to test for memory export explicitly, because the allocation happens before config load. + if (g_settings.export_shared_memory && !Bus::ReallocateMemoryMap(error)) + { + Error::AddPrefix(error, "Failed to reallocate memory map:\n"); + return false; + } + #ifdef ENABLE_RAINTEGRATION if (Host::GetBaseBoolSettingValue("Cheevos", "UseRAIntegration", false)) Achievements::SwitchToRAIntegration(); @@ -996,19 +1006,13 @@ System::GameHash System::GetGameHashFromBuffer(std::string_view exe_name, std::s DiscRegion System::GetRegionForSerial(const std::string_view serial) { static constexpr const std::pair region_prefixes[] = { - {"sces", DiscRegion::PAL}, - {"sced", DiscRegion::PAL}, - {"sles", DiscRegion::PAL}, + {"sces", DiscRegion::PAL}, {"sced", DiscRegion::PAL}, {"sles", DiscRegion::PAL}, {"sled", DiscRegion::PAL}, - {"scps", DiscRegion::NTSC_J}, - {"slps", DiscRegion::NTSC_J}, - {"slpm", DiscRegion::NTSC_J}, - {"sczs", DiscRegion::NTSC_J}, - {"papx", DiscRegion::NTSC_J}, + {"scps", DiscRegion::NTSC_J}, {"slps", DiscRegion::NTSC_J}, {"slpm", DiscRegion::NTSC_J}, + {"sczs", DiscRegion::NTSC_J}, {"papx", DiscRegion::NTSC_J}, - {"scus", DiscRegion::NTSC_U}, - {"slus", DiscRegion::NTSC_U}, + {"scus", DiscRegion::NTSC_U}, {"slus", DiscRegion::NTSC_U}, }; for (const auto& [prefix, region] : region_prefixes) @@ -4360,6 +4364,16 @@ void System::CheckForSettingsChanges(const Settings& old_settings) } #endif + if (g_settings.export_shared_memory != old_settings.export_shared_memory) [[unlikely]] + { + Error error; + if (!Bus::ReallocateMemoryMap(&error)) [[unlikely]] + { + ERROR_LOG(error.GetDescription()); + Panic("Failed to reallocate memory map. The log may contain more information."); + } + } + if (g_settings.log_level != old_settings.log_level || g_settings.log_filter != old_settings.log_filter || g_settings.log_timestamps != old_settings.log_timestamps || g_settings.log_to_console != old_settings.log_to_console || diff --git a/src/core/system.h b/src/core/system.h index 6164c5853..6bddb1077 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -341,9 +341,6 @@ void ApplyCheatCode(const CheatCode& code); /// Sets or clears the provided cheat list, applying every frame. void SetCheatList(std::unique_ptr cheats); -/// Checks for settings changes, std::move() the old settings away for comparing beforehand. -void CheckForSettingsChanges(const Settings& old_settings); - /// Updates throttler. void UpdateSpeedLimiterState(); diff --git a/src/duckstation-qt/advancedsettingswidget.cpp b/src/duckstation-qt/advancedsettingswidget.cpp index 73afc2125..472d8cd7a 100644 --- a/src/duckstation-qt/advancedsettingswidget.cpp +++ b/src/duckstation-qt/advancedsettingswidget.cpp @@ -263,6 +263,8 @@ void AdvancedSettingsWidget::addTweakOptions() addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Allow Booting Without SBI File"), "CDROM", "AllowBootingWithoutSBIFile", false); + addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Export Shared Memory"), "Hacks", "ExportSharedMemory", + false); addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Enable PINE"), "PINE", "Enabled", false); addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("PINE Slot"), "PINE", "Slot", 0, 65535, Settings::DEFAULT_PINE_SLOT); @@ -299,6 +301,7 @@ void AdvancedSettingsWidget::onResetToDefaultClicked() Settings::DEFAULT_CDROM_MECHACON_VERSION); // CDROM Mechacon Version setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // CDROM Region Check setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Allow booting without SBI file + setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Export Shared Memory setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Enable PINE setIntRangeTweakOption(m_ui.tweakOptionTable, i++, Settings::DEFAULT_PINE_SLOT); // PINE Slot setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Enable PCDRV @@ -322,6 +325,7 @@ void AdvancedSettingsWidget::onResetToDefaultClicked() sif->DeleteValue("Hacks", "DMAHaltTicks"); sif->DeleteValue("Hacks", "GPUFIFOSize"); sif->DeleteValue("Hacks", "GPUMaxRunAhead"); + sif->DeleteValue("Hacks", "ExportSharedMemory"); sif->DeleteValue("CPU", "RecompilerMemoryExceptions"); sif->DeleteValue("CPU", "RecompilerBlockLinking"); sif->DeleteValue("CPU", "FastmemMode"); diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 0ab86f82a..508152acf 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -46,6 +46,7 @@ #include "scmversion/scmversion.h" #include "imgui.h" +#include "core/bus.h" #include #include @@ -2513,7 +2514,7 @@ bool QtHost::RunSetupWizard() int main(int argc, char* argv[]) { - CrashHandler::Install(); + CrashHandler::Install(&Bus::CleanupMemoryMap); QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); QtHost::RegisterTypes();