Reimplement __wups_getreent to be per plugin and (hopefully) future proof

This commit is contained in:
Maschell 2026-04-01 11:31:19 +02:00
parent 2c76941e93
commit fa90de3802
2 changed files with 150 additions and 50 deletions

View File

@ -4,8 +4,18 @@
#include <coreinit/debug.h>
#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
#endif
#define WUPS_DEBUG_WARN(fmt, ...) OSReport("[%s] " fmt, wups_meta_info_dump, ##__VA_ARGS__)

View File

@ -3,16 +3,16 @@
#include <cstring>
#include <stdint.h>
#include <stdlib.h>
#include <wups/wups_debug.h>
#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<struct _reent *>(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;
}