From 77ab748eb4d7c4a655dcad53316baa72fd4712d3 Mon Sep 17 00:00:00 2001 From: Maschell Date: Wed, 8 Apr 2026 13:15:19 +0200 Subject: [PATCH] Move reent logic into the wupsbackend --- include/wups/hooks.h | 18 +- include/wups/meta.h | 3 +- include/wups/reent_internal.h | 35 ++++ include/wups/wups_debug.h | 2 +- libraries/libwups/main.cpp | 24 ++- libraries/libwups/wups_reent.cpp | 210 +++++++-------------- libraries/libwups/wups_thread_specific.cpp | 57 ------ libraries/libwups/wups_thread_specific.h | 18 -- 8 files changed, 139 insertions(+), 228 deletions(-) create mode 100644 include/wups/reent_internal.h delete mode 100644 libraries/libwups/wups_thread_specific.cpp delete mode 100644 libraries/libwups/wups_thread_specific.h diff --git a/include/wups/hooks.h b/include/wups/hooks.h index 3b1e090..ed537b5 100644 --- a/include/wups/hooks.h +++ b/include/wups/hooks.h @@ -19,6 +19,7 @@ #include "common.h" #include #include +#include #include #ifdef __cplusplus @@ -59,10 +60,11 @@ typedef enum wups_loader_hook_type_t { WUPS_LOADER_HOOK_APPLICATION_REQUESTS_EXIT, /* Called when an application wants to exit */ WUPS_LOADER_HOOK_APPLICATION_ENDS, /* Called when an application ends */ - WUPS_LOADER_HOOK_INIT_STORAGE, /* Only for internal usage */ - WUPS_LOADER_HOOK_INIT_CONFIG, /* Only for internal usage */ - WUPS_LOADER_HOOK_INIT_BUTTON_COMBO, /* Only for internal usage */ - WUPS_LOADER_HOOK_INIT_WUT_THREAD, /* Only for internal usage */ + WUPS_LOADER_HOOK_INIT_STORAGE, /* Only for internal usage */ + WUPS_LOADER_HOOK_INIT_CONFIG, /* Only for internal usage */ + WUPS_LOADER_HOOK_INIT_BUTTON_COMBO, /* Only for internal usage */ + WUPS_LOADER_HOOK_INIT_WUT_THREAD, /* Only for internal usage */ + WUPS_LOADER_HOOK_INIT_REENT_FUNCTIONS, /* Only for internal usage */ } wups_loader_hook_type_t; typedef struct wups_loader_hook_t { @@ -142,6 +144,14 @@ typedef struct wups_loader_hook_t { return WUPSButtonComboAPI_InitInternal(args); \ } +#define WUPS_INIT_REENT_FUNCTIONS() \ + __EXTERN_C_MACRO void WUPSReentAPI_InitInternal(wups_loader_init_reent_args_t_); \ + void wups_init_reent_functions(wups_loader_init_reent_args_t_ args); \ + WUPS_HOOK_EX(WUPS_LOADER_HOOK_INIT_REENT_FUNCTIONS, wups_init_reent_functions); \ + void wups_init_reent_functions(wups_loader_init_reent_args_t_ args) { \ + return WUPSReentAPI_InitInternal(args); \ + } + #define WUPS_USE_WUT_MALLOC() \ __EXTERN_C_MACRO void __init_wut_malloc(); \ void on_init_wut_malloc() { \ diff --git a/include/wups/meta.h b/include/wups/meta.h index 1126920..e0abb51 100644 --- a/include/wups/meta.h +++ b/include/wups/meta.h @@ -38,7 +38,7 @@ extern "C" { #endif -#define WUPS_VERSION_STR "0.9.0" +#define WUPS_VERSION_STR "0.9.1" #define WUPS_PLUGIN_NAME(__plugin_name) \ WUPS_META(name, __plugin_name); \ WUPS_META(wups, WUPS_VERSION_STR); \ @@ -51,6 +51,7 @@ extern "C" { WUPS___FINI_WRAPPER(); \ WUPS_INIT_CONFIG_FUNCTIONS(); \ WUPS_INIT_BUTTON_COMBO_FUNCTIONS(); \ + WUPS_INIT_REENT_FUNCTIONS(); \ WUPS_META(buildtimestamp, __DATE__ " " __TIME__); \ WUPS_SECTION("meta") \ const char wups_meta_plugin_name[] = __plugin_name; \ diff --git a/include/wups/reent_internal.h b/include/wups/reent_internal.h new file mode 100644 index 0000000..8535b1f --- /dev/null +++ b/include/wups/reent_internal.h @@ -0,0 +1,35 @@ +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum wups_loader_init_reent_errors_t_ { + WUPSReent_ERROR_NONE = 0, + WUPSReent_ERROR_GLOBAL_REENT_REQUESTED = 1, + WUPSReent_ERROR_NO_THREAD = 2, +} wups_loader_init_reent_errors_t_; + +typedef void *(*WUPSReent_GetReentContext)(const void *pluginId, wups_loader_init_reent_errors_t_ *outError); +typedef void *(*WUPSReent_SetSentinel)(); +typedef void (*WUPSReent_RestoreHead)(void *oldHead); +typedef bool (*WUPSReent_AddReentContext)(const void *pluginId, void *reentPtr, void (*cleanupFn)(void *), void *oldHead); + +typedef uint32_t WUPS_REENT_API_VERSION; + +#define WUPS_REENT_CUR_API_VERSION 0x01 + +typedef struct wups_loader_init_reent_args_t_ { + WUPS_REENT_API_VERSION version; + WUPSReent_GetReentContext get_context_ptr; + WUPSReent_SetSentinel set_sentinel_ptr; + WUPSReent_RestoreHead restore_head_ptr; + WUPSReent_AddReentContext add_reent_context_ptr; +} wups_loader_init_reent_args_t_; + +void WUPSReentAPI_InitInternal(wups_loader_init_reent_args_t_ args); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/include/wups/wups_debug.h b/include/wups/wups_debug.h index 0d3e85f..0549392 100644 --- a/include/wups/wups_debug.h +++ b/include/wups/wups_debug.h @@ -18,4 +18,4 @@ extern const char wups_meta_info_dump[]; #define WUPS_DEBUG_REPORT(fmt, ...) #endif -#define WUPS_DEBUG_WARN(fmt, ...) OSReport("[%s] " fmt, wups_meta_info_dump, ##__VA_ARGS__) \ No newline at end of file +#define WUPS_DEBUG_WARN(fmt, ...) OSReport("\033[33m[%s] " fmt "\033[0m", wups_meta_info_dump, ##__VA_ARGS__) \ No newline at end of file diff --git a/libraries/libwups/main.cpp b/libraries/libwups/main.cpp index 3415e6e..8cbea69 100644 --- a/libraries/libwups/main.cpp +++ b/libraries/libwups/main.cpp @@ -1,5 +1,6 @@ #include "wups_reent.h" -#include "wups_thread_specific.h" +#include + #include #include #include @@ -44,16 +45,25 @@ extern "C" struct _reent *__getreent(void) { return __wups_getreent(); } -extern "C" void __attribute__((weak)) wut_set_thread_specific(__wut_thread_specific_id id, void *value); +extern const char wups_meta_info_dump[]; + +typedef enum __wut_thread_specific_id { + WUT_THREAD_SPECIFIC_0 = 0, + WUT_THREAD_SPECIFIC_1 = 1, +} __wut_thread_specific_id; -void wut_set_thread_specific(__wut_thread_specific_id id, void *value) { - return wups_set_thread_specific(id, value); -} extern "C" void *__attribute__((weak)) wut_get_thread_specific(__wut_thread_specific_id id); void *wut_get_thread_specific(__wut_thread_specific_id id) { - return wups_get_thread_specific(id); + if ((uint32_t) id == 0x13371337) { // Mechanism to detect if the function was overridden properly + return (void *) 0x42424242; + } + + OSReport("[%s] wups_get_thread_specific: NOT SUPPORTED\n", wups_meta_info_dump); + OSFatal("wups_get_thread_specific: NOT SUPPORTED\n"); + + return nullptr; } @@ -102,6 +112,8 @@ __assert_func(const char *file, } OSFatal(buffer); + while (true) + ; /* NOTREACHED */ } diff --git a/libraries/libwups/wups_reent.cpp b/libraries/libwups/wups_reent.cpp index 01e03d6..cca6eff 100644 --- a/libraries/libwups/wups_reent.cpp +++ b/libraries/libwups/wups_reent.cpp @@ -1,175 +1,103 @@ #include "wups_reent.h" -#include "wups_thread_specific.h" -#include -#include + +#include "wups/hooks.h" +#include "wups/reent_internal.h" +#include "wups/wups_debug.h" +#include +#include #include -#include -#define __WUPS_CONTEXT_THREAD_SPECIFIC_ID WUT_THREAD_SPECIFIC_0 -#define WUPS_REENT_ALLOC_SENTINEL ((__wups_reent_node *) 0xFFFFFFFF) - -extern "C" __attribute__((weak)) void wut_set_thread_specific(__wut_thread_specific_id id, void *value); -extern "C" __attribute__((weak)) void *wut_get_thread_specific(__wut_thread_specific_id); - -typedef uint32_t OSThread; - -extern "C" void OSFatal(const char *); -extern "C" void OSReport(const char *, ...); - -extern "C" OSThread *OSGetCurrentThread(); - -typedef void (*OSThreadCleanupCallbackFn)(OSThread *thread, void *stack); - -extern "C" OSThreadCleanupCallbackFn -OSSetThreadCleanupCallback(OSThread *thread, - OSThreadCleanupCallbackFn callback); - -#define WUPS_REENT_NODE_VERSION 1 -#define WUPS_REENT_NODE_MAGIC 0x57555053 // WUPS -static const int sReentPluginId = 0; - -struct __wups_reent_node { - // FIXED HEADER (Never move or change these offsets!) - uint32_t magic; // Guarantees this is a __wups_reent_node - uint32_t version; - __wups_reent_node *next; - - // Node Version 1 Payload - const void *pluginId; - void *reentPtr; - void (*cleanupFn)(__wups_reent_node *); - OSThreadCleanupCallbackFn savedCleanup; +struct wups_loader_init_reent_args_t { + WUPSReent_GetReentContext get_context_ptr = nullptr; + WUPSReent_SetSentinel set_sentinel_ptr = nullptr; + WUPSReent_RestoreHead restore_head_ptr = nullptr; + WUPSReent_AddReentContext add_reent_context_ptr = nullptr; }; -static void reclaim_reent_trampoline(__wups_reent_node *node) { - WUPS_DEBUG_REPORT("reclaim_reent_trampoline: Destroying node %p (reent: %p)\n", node, node->reentPtr); +static wups_loader_init_reent_args_t __internal_functions = {}; - if (node->reentPtr) { - _reclaim_reent(static_cast<_reent *>(node->reentPtr)); - free(node->reentPtr); +void WUPSReentAPI_InitInternal(wups_loader_init_reent_args_t_ args) { + if (args.version > WUPS_REENT_CUR_API_VERSION) { + OSFatal("Incompatible reent api version"); + return; } - free(node); + WUPS_DEBUG_REPORT("WUPSReentAPI_InitInternal: Initializing reent plugin\n"); + + __internal_functions.get_context_ptr = args.get_context_ptr; + __internal_functions.set_sentinel_ptr = args.set_sentinel_ptr; + __internal_functions.add_reent_context_ptr = args.add_reent_context_ptr; + __internal_functions.restore_head_ptr = args.restore_head_ptr; } -static void __wups_thread_cleanup(OSThread *thread, void *stack) { - auto *head = static_cast<__wups_reent_node *>(wut_get_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID)); +// use variable in the .data section as unique plugin id +static const int sReentPluginId = 0; - if (!head || head == WUPS_REENT_ALLOC_SENTINEL) { - return; - } +void *wups_backend_get_context(const void *id, wups_loader_init_reent_errors_t_ *outError) { + return __internal_functions.get_context_ptr(id, outError); +} - if (head->magic != WUPS_REENT_NODE_MAGIC) { - WUPS_DEBUG_WARN("__wups_thread_cleanup: Unexpected node magic word: %08X (expected %08X).\n", head->magic, WUPS_REENT_NODE_MAGIC); - return; - } +void *wups_backend_set_sentinel() { + return __internal_functions.set_sentinel_ptr(); +} - WUPS_DEBUG_REPORT("__wups_thread_cleanup: Triggered for thread %p\n", thread); +void wups_backend_restore_head(void *head) { + __internal_functions.restore_head_ptr(head); +} - OSThreadCleanupCallbackFn savedCleanup = nullptr; - if (head->version >= 1) { - savedCleanup = head->savedCleanup; - } +bool wups_backend_register_context(const void *pluginId, void *reentPtr, void (*cleanupFn)(void *), void *oldHead) { + return __internal_functions.add_reent_context_ptr(pluginId, reentPtr, cleanupFn, oldHead); +} - // Set to effective global during free to prevent malloc re-entrancy loops - wut_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, WUPS_REENT_ALLOC_SENTINEL); +static void reclaim_reent_trampoline(void *payload) { + WUPS_DEBUG_REPORT("reclaim_reent_trampoline: Destroying reent payload: %p\n", payload); - // Safely iterate the ABI-stable list. - auto *curr = head; - while (curr) { - // Read the "next" pointer BEFORE destroying the current node. - __wups_reent_node *next = curr->next; - - // Trigger the self-destruct sequence. Frees curr - if (curr->cleanupFn) { - curr->cleanupFn(curr); - } - - curr = next; - } - - wut_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, nullptr); - - if (savedCleanup) { - WUPS_DEBUG_REPORT("__wups_thread_cleanup: Chaining to saved cleanup for thread %p\n", thread); - savedCleanup(thread, stack); + if (payload) { + auto *reentPtr = static_cast<_reent *>(payload); + _reclaim_reent(reentPtr); + free(reentPtr); } } struct _reent *__wups_getreent() { - if (!wut_get_thread_specific || !wut_set_thread_specific || OSGetCurrentThread() == nullptr) { + if (!OSGetCurrentThread()) { return _GLOBAL_REENT; } - - auto head = static_cast<__wups_reent_node *>(wut_get_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID)); - - if (head == WUPS_REENT_ALLOC_SENTINEL) { - return _GLOBAL_REENT; + wups_loader_init_reent_errors_t_ error = WUPSReent_ERROR_NONE; + if (void *existingCtx = wups_backend_get_context(&sReentPluginId, &error)) { + return static_cast<_reent *>(existingCtx); + } + switch (error) { + case WUPSReent_ERROR_GLOBAL_REENT_REQUESTED: + case WUPSReent_ERROR_NO_THREAD: + return _GLOBAL_REENT; + case WUPSReent_ERROR_NONE: + break; } - if (head && head->magic != WUPS_REENT_NODE_MAGIC) { - WUPS_DEBUG_WARN("__wups_getreent: Unexpected node magic word: %08X (expected %08X).\n", head->magic, WUPS_REENT_NODE_MAGIC); - return _GLOBAL_REENT; - } - - // Check for already allocated reent ptr. - // (Intentionally not logging here to prevent console spam on the fast path) - const __wups_reent_node *curr = head; - while (curr) { - // Use a memory address as a unique id - if (curr->version >= 1 && curr->pluginId == &sReentPluginId) { - return static_cast<_reent *>(curr->reentPtr); - } - curr = curr->next; - } - - WUPS_DEBUG_REPORT("__wups_getreent: Allocating new context for thread %p\n", OSGetCurrentThread()); - - // If not found allocate a new for THIS plugin. - // Temporarily effectively use global reent during context allocation - wut_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, WUPS_REENT_ALLOC_SENTINEL); - - auto *newNode = static_cast<__wups_reent_node *>(malloc(sizeof(__wups_reent_node))); + auto *oldHead = wups_backend_set_sentinel(); auto *newReent = static_cast(malloc(sizeof(struct _reent))); - - if (!newNode || !newReent) { - WUPS_DEBUG_WARN("__wups_getreent: Failed to allocate context! Falling back to _GLOBAL_REENT.\n"); - if (newNode) { - free(newNode); - } - if (newReent) { - free(newReent); - } - // reset on error - wut_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, head); + if (!newReent) { + wups_backend_restore_head(oldHead); return _GLOBAL_REENT; } + WUPS_DEBUG_REPORT("Allocated context! for thread %p: %p\n", OSGetCurrentThread(), newReent); + _REENT_INIT_PTR(newReent); - newNode->magic = WUPS_REENT_NODE_MAGIC; - newNode->version = WUPS_REENT_NODE_VERSION; - newNode->next = head; - newNode->pluginId = &sReentPluginId; - newNode->reentPtr = newReent; - newNode->cleanupFn = reclaim_reent_trampoline; - newNode->savedCleanup = nullptr; + bool result = wups_backend_register_context( + &sReentPluginId, + newReent, + reclaim_reent_trampoline, + oldHead); - auto oldHead = head; - - // Hook cleanup logic - if (oldHead == nullptr) { - WUPS_DEBUG_REPORT("__wups_getreent: Hooking OSSetThreadCleanupCallback for thread %p\n", OSGetCurrentThread()); - newNode->savedCleanup = OSSetThreadCleanupCallback(OSGetCurrentThread(), &__wups_thread_cleanup); - } else { - WUPS_DEBUG_REPORT("__wups_getreent: Prepending to existing list for thread %p\n", OSGetCurrentThread()); - // We prepend, so we must inherit the saved cleanup from the previous head - if (oldHead->version >= 1) { - newNode->savedCleanup = oldHead->savedCleanup; - oldHead->savedCleanup = nullptr; - } + if (!result) { + WUPS_DEBUG_WARN("Failed to register context for thread %p\n", OSGetCurrentThread()); + _reclaim_reent(newReent); + free(newReent); + wups_backend_restore_head(oldHead); + return _GLOBAL_REENT; } - wut_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, newNode); - return newReent; } \ No newline at end of file diff --git a/libraries/libwups/wups_thread_specific.cpp b/libraries/libwups/wups_thread_specific.cpp deleted file mode 100644 index d04bf8d..0000000 --- a/libraries/libwups/wups_thread_specific.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "wups_thread_specific.h" -#include -#include - -extern "C" void OSFatal(const char *); -extern "C" void OSReport(const char *, ...); - -typedef struct OSThread { - uint8_t other[0x68c]; - void *reserved[5]; -} OSThread; - -static_assert(offsetof(OSThread, reserved) == 0x68c, "OSThread: \"reserved\" at wrong offset"); -static_assert(sizeof(OSThread) == 0x6a0, "OSThread: wrong size"); - -extern "C" const char wups_meta_info_dump[]; - -void wups_set_thread_specific(__wut_thread_specific_id id, void *value) { - OSThread *thread; - asm volatile("lwz %0, -0x20(0)" - : "=r"(thread)); // OSGetCurrentThread() - if (thread != nullptr) { - if (id == WUT_THREAD_SPECIFIC_0) { - thread->reserved[3] = value; - } else if (id == WUT_THREAD_SPECIFIC_1) { - thread->reserved[4] = value; - } else { - OSReport("[%s] wups_set_thread_specific: invalid id\n", wups_meta_info_dump); - OSFatal("wups_set_thread_specific: invalid id"); - } - } else { - OSReport("[%s] wups_set_thread_specific: invalid thread\n", wups_meta_info_dump); - OSFatal("wups_set_thread_specific: invalid thread"); - } -} - -void *wups_get_thread_specific(__wut_thread_specific_id id) { - OSThread *thread; - asm volatile("lwz %0, -0x20(0)" - : "=r"(thread)); // OSGetCurrentThread() - if (thread != nullptr) { - if (id == WUT_THREAD_SPECIFIC_0) { - return thread->reserved[3]; - } else if (id == WUT_THREAD_SPECIFIC_1) { - return thread->reserved[4]; - } else if ((uint32_t) id == 0x13371337) { // Mechanism to detect if the function was overridden properly - return (void *) 0x42424242; - } else { - OSReport("[%s] wups_get_thread_specific: invalid id\n", wups_meta_info_dump); - OSFatal("wups_get_thread_specific: invalid id"); - } - } else { - OSReport("[%s] wups_get_thread_specific: invalid thread\n", wups_meta_info_dump); - OSFatal("wups_get_thread_specific: invalid thread\n"); - } - return nullptr; -} \ No newline at end of file diff --git a/libraries/libwups/wups_thread_specific.h b/libraries/libwups/wups_thread_specific.h deleted file mode 100644 index d534180..0000000 --- a/libraries/libwups/wups_thread_specific.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -typedef enum __wut_thread_specific_id { - WUT_THREAD_SPECIFIC_0 = 0, - WUT_THREAD_SPECIFIC_1 = 1, -} __wut_thread_specific_id; - -#ifdef __cplusplus -extern "C" { -#endif - -void wups_set_thread_specific(__wut_thread_specific_id id, void *value); - -void *wups_get_thread_specific(__wut_thread_specific_id id); - -#ifdef __cplusplus -} -#endif \ No newline at end of file