From 8b19bcc85bee0fbd4042e8a9be255943d1b19722 Mon Sep 17 00:00:00 2001 From: Maschell Date: Fri, 10 Apr 2026 13:45:01 +0200 Subject: [PATCH] Simplify ClearDanglingReentPtr --- Dockerfile | 4 +- source/main.cpp | 1 + source/patcher/hooks_patcher_static.cpp | 10 ---- source/utils/reent.cpp | 68 +++++++++++++++++++------ source/utils/reent.h | 2 - 5 files changed, 56 insertions(+), 29 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2df6487..a0d64fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM ghcr.io/wiiu-env/devkitppc:20260225 -COPY --from=ghcr.io/wiiu-env/wiiumodulesystem:reentfix-dev-20260403-5ca1144 /artifacts $DEVKITPRO -COPY --from=ghcr.io/wiiu-env/wiiupluginsystem:abifix-dev-20260408-77ab748 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/wiiumodulesystem:reentfix-dev-20260410-ae8bf4a /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/wiiupluginsystem:abifix-dev-20260410-b1ab874 /artifacts $DEVKITPRO COPY --from=ghcr.io/wiiu-env/libfunctionpatcher:20260331 /artifacts $DEVKITPRO COPY --from=ghcr.io/wiiu-env/libmappedmemory:20260331 /artifacts $DEVKITPRO COPY --from=ghcr.io/wiiu-env/libwupsbackend:20260331 /artifacts $DEVKITPRO diff --git a/source/main.cpp b/source/main.cpp index d3be681..f2a5235 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -190,6 +190,7 @@ WUMS_APPLICATION_STARTS() { initLogging(); + // Only call this EXACTLY once per application cycle ClearDanglingReentPtr(); std::lock_guard lock(gLoadedDataMutex); diff --git a/source/patcher/hooks_patcher_static.cpp b/source/patcher/hooks_patcher_static.cpp index 1b68276..330f2b5 100644 --- a/source/patcher/hooks_patcher_static.cpp +++ b/source/patcher/hooks_patcher_static.cpp @@ -277,15 +277,6 @@ DECL_FUNCTION(uint32_t, KiGetAppSymbolName, uint32_t addr, char *buffer, int32_t #pragma GCC pop_options - -DECL_FUNCTION(uint32_t, SomeExitHook) { - // Thats the last thing called in __PPCExit - const auto res = real_SomeExitHook(); - - MarkReentNodesForDeletion(); - return res; -} - function_replacement_data_t method_hooks_static[] __attribute__((section(".data"))) = { REPLACE_FUNCTION(GX2SwapScanBuffers, LIBRARY_GX2, GX2SwapScanBuffers), REPLACE_FUNCTION(GX2SetTVBuffer, LIBRARY_GX2, GX2SetTVBuffer), @@ -297,7 +288,6 @@ function_replacement_data_t method_hooks_static[] __attribute__((section(".data" REPLACE_FUNCTION(WPADRead, LIBRARY_PADSCORE, WPADRead), REPLACE_FUNCTION_VIA_ADDRESS(SC17_FindClosestSymbol, 0xfff10218, 0xfff10218), REPLACE_FUNCTION_VIA_ADDRESS(KiGetAppSymbolName, 0xfff0e3a0, 0xfff0e3a0), - REPLACE_FUNCTION_VIA_ADDRESS(SomeExitHook, 0x3201C400 + 0x40b4c, 0x101C400 + 0x40b4c), }; uint32_t method_hooks_static_size __attribute__((section(".data"))) = sizeof(method_hooks_static) / sizeof(function_replacement_data_t); diff --git a/source/utils/reent.cpp b/source/utils/reent.cpp index 6fa379b..880b602 100644 --- a/source/utils/reent.cpp +++ b/source/utils/reent.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #define __WUPS_CONTEXT_THREAD_SPECIFIC_ID 0 @@ -64,24 +65,65 @@ struct __wups_reent_node { OSThreadCleanupCallbackFn savedCleanup; }; + namespace { - std::vector<__wups_reent_node *> sGlobalNodesCopy; + std::unordered_set<__wups_reent_node *> sGlobalNodesSeen; std::vector<__wups_reent_node *> sGlobalNodes; std::recursive_mutex sGlobalNodesMutex; + + void removeNodeFromListsLocked(__wups_reent_node *curr) { + std::lock_guard lock(sGlobalNodesMutex); + if (const auto it = std::ranges::find(sGlobalNodes, curr); it != sGlobalNodes.end()) { + *it = sGlobalNodes.back(); + sGlobalNodes.pop_back(); + } + sGlobalNodesSeen.erase(curr); + } } // namespace -void MarkReentNodesForDeletion() { - sGlobalNodesCopy = std::move(sGlobalNodes); -} void ClearDanglingReentPtr() { - for (auto nodeToFree : sGlobalNodesCopy) { + std::lock_guard lock(sGlobalNodesMutex); + + DEBUG_FUNCTION_LINE_ERR("Before clean up having %d entries", sGlobalNodes.size()); + + // This function is expected to be called exactly once at the start of each new application cycle. + // It acts as a garbage collector for nodes left behind by the previous application. + // Leftover nodes typically occur when threads are forcefully killed before they can execute + // their cleanup callbacks, or if a thread's cleanup function was wrongly overridden. + // + // Mechanism: Since threads do not survive across application boundaries, any node we + // observe across multiple cycles is guaranteed to be a dangling pointer from a dead thread. + std::erase_if(sGlobalNodes, [](__wups_reent_node *ptr) { + if (ptr == nullptr) { + return true; + } + + // Try to register the pointer in our historical "seen" tracker. + auto [iterator, isNewValue] = sGlobalNodesSeen.insert(ptr); + + if (isNewValue) { + // If it was newly inserted, it might be a valid node created during the current + // application cycle (e.g., initialized via a hook in an RPL's init function). + // We keep it in the vector for now. + return false; + } + + // Otherwise, we have already seen this address in a previous cycle. + // This means the node belongs to a dead thread from an older application. + // It is now safe to execute its payload cleanup and free the memory. + auto *nodeToFree = *iterator; if (nodeToFree->cleanupFn) { nodeToFree->cleanupFn(nodeToFree->reentPtr); } free(nodeToFree); - } - sGlobalNodesCopy.clear(); + + // Make to remove it from the "seen" list as well. + sGlobalNodesSeen.erase(iterator); + return true; + }); + + DEBUG_FUNCTION_LINE_ERR("After clean up having %d entries", sGlobalNodes.size()); } static void __wups_thread_cleanup(OSThread *thread, void *stack) { @@ -104,10 +146,7 @@ static void __wups_thread_cleanup(OSThread *thread, void *stack) { curr->cleanupFn(curr->reentPtr); } - { - std::lock_guard lock(sGlobalNodesMutex); - std::erase(sGlobalNodes, curr); - } + removeNodeFromListsLocked(curr); free(curr); curr = next; @@ -299,11 +338,10 @@ void ClearReentDataForPlugins(const std::vector &plugins) { if (nodeToFree->cleanupFn) { nodeToFree->cleanupFn(nodeToFree->reentPtr); } + + removeNodeFromListsLocked(nodeToFree); free(nodeToFree); - { - std::lock_guard lock(sGlobalNodesMutex); - std::erase(sGlobalNodes, nodeToFree); - } + nodeToFree = nextNode; } wups_backend_restore_head(oldHead); diff --git a/source/utils/reent.h b/source/utils/reent.h index 730dd00..51a1736 100644 --- a/source/utils/reent.h +++ b/source/utils/reent.h @@ -14,6 +14,4 @@ bool wups_backend_register_context(const void *pluginId, void *reentPtr, void (* void ClearReentDataForPlugins(const std::vector &plugins); -void MarkReentNodesForDeletion(); - void ClearDanglingReentPtr();