Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions include/wups/wups_debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
165 changes: 128 additions & 37 deletions libraries/libwups/wups_reent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +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 *, ...);
Expand All @@ -24,62 +25,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 *plugin_id;
void *reent_ptr;
void (*cleanup_fn)(__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->reent_ptr);

if (node->reent_ptr) {
_reclaim_reent((struct _reent *) node->reent_ptr);
free(node->reent_ptr);
}
free(node);
}

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));

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 (!head || head == WUPS_REENT_ALLOC_SENTINEL) {
return;
}

if (context->savedCleanup) {
context->savedCleanup(thread, stack);
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;
}

_reclaim_reent(&context->reent);
WUPS_DEBUG_REPORT("__wups_thread_cleanup: Triggered for thread %p\n", thread);

OSThreadCleanupCallbackFn savedCleanup = nullptr;
if (head->version >= 1) {
savedCleanup = head->savedCleanup;
}

// Use global reent during free since the current reent is getting freed
wut_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, _GLOBAL_REENT);
// 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);

free(context);
// Safely iterate the ABI-stable list.
__wups_reent_node *curr = head;
while (curr) {
// Read the "next" pointer BEFORE destroying the current node.
__wups_reent_node *next = curr->next;

wut_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, NULL);
// Trigger the self-destruct sequence. Frees curr
if (curr->cleanup_fn) {
curr->cleanup_fn(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() {
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));

if (head == WUPS_REENT_ALLOC_SENTINEL) {
return _GLOBAL_REENT;
}

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);
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;
}

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;
// 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->plugin_id == &sReentPluginId) {
return static_cast<_reent *>(curr->reent_ptr);
}
curr = curr->next;
}

WUPS_DEBUG_REPORT("__wups_getreent: Allocating new context for thread %p\n", OSGetCurrentThread());

_REENT_INIT_PTR(&context->reent);
context->savedCleanup = OSSetThreadCleanupCallback(OSGetCurrentThread(), &__wups_thread_cleanup);
// 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);

wut_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, context);
auto *new_node = static_cast<__wups_reent_node *>(malloc(sizeof(__wups_reent_node)));
_reent *new_reent = static_cast<struct _reent *>(malloc(sizeof(struct _reent)));

if (!new_node || !new_reent) {
WUPS_DEBUG_WARN("__wups_getreent: Failed to allocate context! Falling back to _GLOBAL_REENT.\n");
if (new_node) {
free(new_node);
}
if (new_reent) {
free(new_reent);
}
// reset on error
wut_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, head);
return _GLOBAL_REENT;
}

return &context->reent;
}
_REENT_INIT_PTR(new_reent);

new_node->magic = WUPS_REENT_NODE_MAGIC;
new_node->version = WUPS_REENT_NODE_VERSION;
new_node->next = head;
new_node->plugin_id = &sReentPluginId;
new_node->reent_ptr = new_reent;
new_node->cleanup_fn = reclaim_reent_trampoline;
new_node->savedCleanup = nullptr;

auto old_head = head;

// Hook cleanup logic
if (old_head == nullptr) {
WUPS_DEBUG_REPORT("__wups_getreent: Hooking OSSetThreadCleanupCallback for thread %p\n", OSGetCurrentThread());
new_node->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 (old_head->version >= 1) {
new_node->savedCleanup = old_head->savedCleanup;
old_head->savedCleanup = nullptr;
}
}

wut_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, new_node);

return new_reent;
}
Loading