From b10063b1a603db2ae09276945c5339d8f3a65105 Mon Sep 17 00:00:00 2001 From: Maschell Date: Wed, 1 Apr 2026 11:31:19 +0200 Subject: [PATCH] Reimplement __wups_getreent to be per plugin and (hopefully) future proof --- include/wups/wups_debug.h | 14 ++- libraries/libwups/wups_reent.cpp | 186 +++++++++++++++++++++++-------- 2 files changed, 150 insertions(+), 50 deletions(-) diff --git a/include/wups/wups_debug.h b/include/wups/wups_debug.h index c966fa8..0d3e85f 100644 --- a/include/wups/wups_debug.h +++ b/include/wups/wups_debug.h @@ -4,8 +4,18 @@ #include #endif +#ifdef __cplusplus +extern "C" { +#endif +extern const char wups_meta_info_dump[]; +#ifdef __cplusplus +} +#endif + #ifdef DEBUG -#define WUPS_DEBUG_REPORT(fmt, ...) OSReport(fmt, ##__VA_ARGS__) +#define WUPS_DEBUG_REPORT(fmt, ...) OSReport("[%s] " fmt, wups_meta_info_dump, ##__VA_ARGS__) #else #define WUPS_DEBUG_REPORT(fmt, ...) -#endif \ No newline at end of file +#endif + +#define WUPS_DEBUG_WARN(fmt, ...) OSReport("[%s] " fmt, wups_meta_info_dump, ##__VA_ARGS__) \ No newline at end of file diff --git a/libraries/libwups/wups_reent.cpp b/libraries/libwups/wups_reent.cpp index 642c23c..01e03d6 100644 --- a/libraries/libwups/wups_reent.cpp +++ b/libraries/libwups/wups_reent.cpp @@ -3,16 +3,16 @@ #include #include #include +#include -#define __WUPS_CONTEXT_THREAD_SPECIFIC_ID WUT_THREAD_SPECIFIC_1 +#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 const char wups_meta_info_dump[]; - extern "C" void OSFatal(const char *); extern "C" void OSReport(const char *, ...); @@ -24,62 +24,152 @@ extern "C" OSThreadCleanupCallbackFn OSSetThreadCleanupCallback(OSThread *thread, OSThreadCleanupCallbackFn callback); -struct __wups_thread_context { - struct _reent reent; +#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; }; -static void -__wups_thread_cleanup(OSThread *thread, - void *stack) { - struct __wups_thread_context *context; +static void reclaim_reent_trampoline(__wups_reent_node *node) { + WUPS_DEBUG_REPORT("reclaim_reent_trampoline: Destroying node %p (reent: %p)\n", node, node->reentPtr); - context = (struct __wups_thread_context *) wut_get_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID); - if (!context || &context->reent == _GLOBAL_REENT) { - OSReport("[%s] __wups_thread_cleanup: Context was NULL or reent was global\n", wups_meta_info_dump); - OSFatal("__wups_thread_cleanup: Context was NULL or reent was global"); + if (node->reentPtr) { + _reclaim_reent(static_cast<_reent *>(node->reentPtr)); + free(node->reentPtr); } - - if (context->savedCleanup) { - context->savedCleanup(thread, stack); - } - - _reclaim_reent(&context->reent); - - // Use global reent during free since the current reent is getting freed - wut_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, _GLOBAL_REENT); - - free(context); - - wut_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, NULL); + free(node); } -struct _reent * -__wups_getreent() { +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)); + + if (!head || head == WUPS_REENT_ALLOC_SENTINEL) { + return; + } + + 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; + } + + WUPS_DEBUG_REPORT("__wups_thread_cleanup: Triggered for thread %p\n", thread); + + OSThreadCleanupCallbackFn savedCleanup = nullptr; + if (head->version >= 1) { + savedCleanup = head->savedCleanup; + } + + // 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); + + // 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); + } +} + +struct _reent *__wups_getreent() { if (!wut_get_thread_specific || !wut_set_thread_specific || OSGetCurrentThread() == nullptr) { return _GLOBAL_REENT; } - struct __wups_thread_context *context; + auto head = static_cast<__wups_reent_node *>(wut_get_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID)); - context = (struct __wups_thread_context *) wut_get_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID); - if (!context) { - // Temporarily use global reent during context allocation - wut_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, _GLOBAL_REENT); - - context = (struct __wups_thread_context *) malloc(sizeof(*context)); - if (!context) { - OSReport("[%s] __wups_getreent: Failed to allocate reent context\n", wups_meta_info_dump); - OSFatal("__wups_getreent: Failed to allocate reent context"); - wut_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, NULL); - return NULL; - } - - _REENT_INIT_PTR(&context->reent); - context->savedCleanup = OSSetThreadCleanupCallback(OSGetCurrentThread(), &__wups_thread_cleanup); - - wut_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, context); + if (head == WUPS_REENT_ALLOC_SENTINEL) { + return _GLOBAL_REENT; } - return &context->reent; -} + 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 *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); + return _GLOBAL_REENT; + } + + _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; + + 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; + } + } + + wut_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, newNode); + + return newReent; +} \ No newline at end of file