#include "libc64/__osMalloc.h" #include #include "libultra/libultra.h" #include "terminal.h" #define OS_MALLOC_MAGIC (s16)'ss' // magic number for an OSMemBlock #define OS_MALLOC_BLOCK_OK(block) ((block) != NULL && (block)->magic == OS_MALLOC_MAGIC) // check if OSMemBlock structure is OK #define OS_MALLOC_DATA2BLOCK(data) ((OSMemBlock*)((u32)(data) - sizeof(OSMemBlock))) // get memblock data pointer from OSMemBlock #define OS_MALLOC_BLOCK2DATA(block) ((u8*)(block) + sizeof(OSMemBlock)) // get OSMemBlock pointer from data // Gets the pointer to the next OSMemBlock immediately following this block in RAM, may or may not be a valid OSMemBlock #define OS_MALLOC_NEXTMEMBLOCK(block) ((OSMemBlock*)((u32)(block) + sizeof(OSMemBlock) + (block)->size)) int __osMalloc_FreeBlockTest_Enable = FALSE; static void setDebugInfo(OSMemBlock* block, const char* file, s32 line, OSArena* arena) { block->file = file; block->line = line; block->threadId = osGetThreadId(NULL); block->arena = arena; block->time = osGetTime(); } static void arena_lock_init(OSArena* arena) { static OSMesg arena_lock_msg; osCreateMesgQueue(&arena->lockQueue, &arena_lock_msg, OS_MESG_BLOCK); } static void arena_lock(OSArena* arena) { osSendMesg(&arena->lockQueue, NULL, OS_MESG_BLOCK); } static void arena_unlock(OSArena* arena) { osRecvMesg(&arena->lockQueue, NULL, OS_MESG_BLOCK); } static OSMemBlock* get_block_next(OSMemBlock* block) { if (block->next != NULL && !OS_MALLOC_BLOCK_OK(block->next)) { // Emergency! Memory leak discovered! OSReport(VT_COL(RED, WHITE) "緊急事態!メモリリーク発見! (block=%08x)\n" VT_RST, block->next); OSPanic(__FILE__, 133, ""); block->next = NULL; // @BUG - OSPanic halts CPU execution, this is pointless } return block->next; } static OSMemBlock* get_block_prev(OSMemBlock* block) { // @BUG - this shouldn't check for block->prev != NULL if (block->prev != NULL && !OS_MALLOC_BLOCK_OK(block->prev)) { // Emergency! Memory leak discovered! OSReport(VT_COL(RED, WHITE) "緊急事態!メモリリーク発見! (block=%08x)\n" VT_RST, block->prev); OSPanic(__FILE__, 144, ""); block->prev = NULL; // @BUG - OSPanic halts CPU execution, this is pointless } return block->prev; } static OSMemBlock* search_last_block(OSArena* arena) { OSMemBlock* block = NULL; if (arena != NULL) { OSMemBlock* current = arena->head; if (OS_MALLOC_BLOCK_OK(current)) { while (current != NULL) { block = current; current = get_block_next(current); } } } return block; } extern void __osMallocInit(OSArena* arena, u8* base, s32 size) { bzero(arena, sizeof(OSArena)); arena_lock_init(arena); __osMallocAddBlock(arena, base, size); arena->initialized = TRUE; } extern void __osMallocAddBlock(OSArena* arena, u8* base, s32 size) { s32 align_size; OSMemBlock* block; OSMemBlock* last; if (base != NULL) { block = (OSMemBlock*)ALIGN_NEXT((u32)base, 32); align_size = ALIGN_PREV(size - ((u32)block - (u32)base), 32); if (align_size > (int)sizeof(OSMemBlock)) { memset(block, 0xAB, align_size); block->next = NULL; block->prev = NULL; block->size = align_size - sizeof(OSMemBlock); block->free = TRUE; block->magic = OS_MALLOC_MAGIC; arena_lock(arena); last = search_last_block(arena); if (last == NULL) { arena->head = block; arena->base = base; } else { block->prev = last; last->next = block; } arena_unlock(arena); } } } static void destroy_all_block(OSArena* arena) { OSMemBlock* block; OSMemBlock* next; arena_lock(arena); block = arena->head; while (block != NULL) { next = get_block_next(block); memset(block, 0xAB, block->size + sizeof(OSMemBlock)); block = next; } arena_unlock(arena); } extern void __osMallocCleanup(OSArena* arena) { destroy_all_block(arena); bzero(arena, sizeof(OSArena)); } extern BOOL __osMallocIsInitalized(OSArena* arena) { return arena->initialized; } static void __osMalloc_FreeBlockTest(OSArena* arena, OSMemBlock* block) { if (__osMalloc_FreeBlockTest_Enable) { u32* s = (u32*)(OS_MALLOC_BLOCK2DATA(block)); u32* e = (u32*)(OS_MALLOC_BLOCK2DATA(block) + block->size); u32* p; for (p = s; p < e; p++) { u32 v = *p; if (v != 0xABABABAB && v != 0xEFEFEFEF) { // Emergency! Memory leak detected! OSReport(VT_COL(RED, WHITE) "緊急事態!メモリリーク検出! (block=%08x s=%08x e=%08x p=%08x)\n" VT_RST, block, s, e, p); __osDisplayArena(arena); OSPanic(__FILE__, 300, ""); break; } } } } static void* __osMallocAlign_NoLock(OSArena* arena, u32 size, u32 align) { OSMemBlock* aligned_block; OSMemBlock* new_next; int alignment_bytes; OSMemBlock* block; u8* data_p = NULL; OSMemBlock* next; u32 full_size; u32 mask; int remain; size = ALIGN_NEXT(size, 32); full_size = ALIGN_NEXT(size, 32) + sizeof(OSMemBlock); if (align <= 16) { align = 16; } else if (align <= 32) { align = 32; } else if (align <= 64) { align = 64; } else if (align <= 128) { align = 128; } else if (align <= 256) { align = 256; } else if (align <= 1024) { align = 1024; } else { align = 8; } block = arena->head; mask = align - 1; while (block != NULL) { if (block->free) { remain = ((u32)block + sizeof(OSMemBlock)) & mask; alignment_bytes = remain == 0 ? 0 : align - remain; aligned_block = (OSMemBlock*)((u32)block + alignment_bytes); if (block->size - alignment_bytes >= size) { if (arena->flags & OSArena_FLAG_FREE_BLOCK_TEST) { __osMalloc_FreeBlockTest(arena, block); } if (block != aligned_block) { memmove(aligned_block, block, sizeof(OSMemBlock)); block = aligned_block; block->size -= alignment_bytes; if (block->prev != NULL) { block->prev->next = block; block->prev->size += alignment_bytes; // do not orphan alignment bytes } else { arena->head = block; } if (block->next != NULL) { block->next->prev = block; } } if (block->size > full_size) { new_next = (OSMemBlock*)((u32)block + full_size); new_next->next = get_block_next(block); new_next->prev = block; new_next->size = block->size - full_size; new_next->free = TRUE; new_next->magic = OS_MALLOC_MAGIC; block->next = new_next; block->size = size; next = get_block_next(new_next); if (next != NULL) { next->prev = new_next; } } block->free = FALSE; setDebugInfo(block, NULL, 0, arena); data_p = OS_MALLOC_BLOCK2DATA(block); if (arena->flags & OSArena_FLAG_CLEAR_MEM_ON_ALLOC) { memset(data_p, 0xCD, size); } break; } } block = get_block_next(block); } return data_p; } static void* __osMalloc_NoLock(OSArena* arena, u32 size) { return __osMallocAlign_NoLock(arena, size, 0); } extern void* __osMallocAlign(OSArena* arena, u32 size, u32 align) { void* ret; arena_lock(arena); ret = __osMallocAlign_NoLock(arena, size, align); arena_unlock(arena); return ret; } extern void* __osMalloc(OSArena* arena, u32 size) { return __osMallocAlign(arena, size, 0); } extern void* __osMallocR(OSArena* arena, u32 size) { OSMemBlock* block; OSMemBlock* next; OSMemBlock* n; u8* ret = NULL; u32 full_size; size = ALIGN_NEXT(size, 32); full_size = ALIGN_NEXT(size, 32) + sizeof(OSMemBlock); arena_lock(arena); block = search_last_block(arena); while (block != NULL) { if (block->free && block->size >= size) { if (arena->flags & OSArena_FLAG_FREE_BLOCK_TEST) { __osMalloc_FreeBlockTest(arena, block); } if (block->size > full_size) { next = (OSMemBlock*)((u32)block + block->size - size); next->next = get_block_next(block); next->prev = block; next->size = size; next->magic = OS_MALLOC_MAGIC; block->next = next; block->size -= full_size; n = get_block_next(next); if (n != NULL) { n->prev = next; } block = next; } block->free = FALSE; setDebugInfo(block, NULL, 0, arena); ret = OS_MALLOC_BLOCK2DATA(block); if (arena->flags & OSArena_FLAG_CLEAR_MEM_ON_ALLOC) { memset(ret, 0xCD, size); } break; } block = get_block_prev(block); } arena_unlock(arena); return ret; } static void __osFree_NoLock(OSArena* arena, void* ptr) { OSMemBlock* block = OS_MALLOC_DATA2BLOCK(ptr); OSMemBlock* next; OSMemBlock* prev; OSMemBlock* temp; if (ptr != NULL) { if (!OS_MALLOC_BLOCK_OK(block)) { OSReport(VT_COL(RED, WHITE) "__osFree:不正解放(%08x)\n" VT_RST, ptr); // __osFree: irregular deallocation OSPanic(__FILE__, 738, ""); return; } if (block->free) { OSReport(VT_COL(RED, WHITE) "__osFree:二重解放(%08x)\n" VT_RST, ptr); // __osFree: double deallocation OSPanic(__FILE__, 743, ""); return; } if (block->arena != arena && arena != NULL) { OSReport(VT_COL(RED, WHITE) "__osFree:確保時と違う方法で解放しようとした (%08x:%08x)\n" VT_RST, arena, block->arena); // __osFree: attempt to release memory in a different arena from where it was allocated OSPanic(__FILE__, 750, ""); return; } next = get_block_next(block); prev = get_block_prev(block); block->free = TRUE; setDebugInfo(block, NULL, 0, arena); if (arena->flags & OSArena_FLAG_CLEAR_MEM_ON_FREE) { memset(OS_MALLOC_BLOCK2DATA(block), 0xEF, block->size); } if (next == OS_MALLOC_NEXTMEMBLOCK(block)) { if (next->free) { temp = get_block_next(next); if (temp != NULL) { temp->prev = block; } block->size += next->size + sizeof(OSMemBlock); if (arena->flags & OSArena_FLAG_CLEAR_MEM_ON_FREE) { memset(next, 0xEF, sizeof(OSMemBlock)); } block->next = temp; next = temp; } } if (prev != NULL && prev->free && block == OS_MALLOC_NEXTMEMBLOCK(prev)) { if (next != NULL) { next->prev = prev; } prev->next = next; prev->size += block->size + sizeof(OSMemBlock); if (arena->flags & OSArena_FLAG_CLEAR_MEM_ON_FREE) { memset(block, 0xEF, sizeof(OSMemBlock)); } } } } extern void __osFree(OSArena* arena, void* ptr) { arena_lock(arena); __osFree_NoLock(arena, ptr); arena_unlock(arena); } // @fabricated, most likely suspect from DnM+'s symbol map static void* __osFree_NoLock_DEBUG(OSArena* arena, void* ptr) { // __osFree: irregular deallocation OSReport(VT_COL(RED, WHITE) "__osFree:不正解放(%08x) [%s:%d ]\n" VT_RST); // __osFree: double deallocation OSReport(VT_COL(RED, WHITE) "__osFree:二重解放(%08x) [%s:%d ]\n" VT_RST); } #pragma force_active on extern void* __osRealloc(OSArena* arena, void* ptr, u32 size) { void* new_ptr; OSMemBlock* orig_block; OSMemBlock* next; OSMemBlock* temp; OSMemBlock* new_next; u32 full_size; u32 need_size; orig_block = OS_MALLOC_DATA2BLOCK(ptr); size = ALIGN_NEXT(size, 32); full_size = ALIGN_NEXT(size, 32) + sizeof(OSMemBlock); arena_lock(arena); if (ptr == NULL) { ptr = __osMalloc_NoLock(arena, size); } else if (size == 0) { __osFree_NoLock(arena, ptr); ptr = NULL; } else if (size != orig_block->size) { if (size > orig_block->size) { next = get_block_next(orig_block); need_size = size - orig_block->size; if (next == OS_MALLOC_NEXTMEMBLOCK(orig_block) && next->free && next->size >= need_size) { OSMemBlock* new_next = (OSMemBlock*)((u32)next + need_size); next->size -= need_size; temp = get_block_next(next); if (temp != NULL) { temp->prev = new_next; } orig_block->next = new_next; orig_block->size = size; memmove(new_next, next, sizeof(OSMemBlock)); } else { new_ptr = __osMalloc_NoLock(arena, size); if (new_ptr != NULL) { memmove(new_ptr, ptr, orig_block->size); __osFree_NoLock(arena, ptr); } ptr = new_ptr; } } else if (size < orig_block->size) { next = get_block_next(orig_block); if (next != NULL && next->free) { OSMemBlock* new_next = (OSMemBlock*)((u32)orig_block + full_size); *new_next = *next; new_next->size += orig_block->size - size; orig_block->next = new_next; orig_block->size = size; temp = get_block_next(new_next); if (temp != NULL) { temp->prev = new_next; } } else if (size + sizeof(OSMemBlock) < orig_block->size) { new_next = (OSMemBlock*)((u32)orig_block + full_size); new_next->next = get_block_next(orig_block); new_next->prev = orig_block; new_next->size = orig_block->size - full_size; new_next->free = TRUE; new_next->magic = OS_MALLOC_MAGIC; orig_block->next = new_next; orig_block->size = size; temp = get_block_next(new_next); if (temp != NULL) { temp->prev = new_next; } } else { ptr = NULL; } } } arena_unlock(arena); return ptr; } #pragma force_active reset static int __osAnalyzeArena(OSArena* arena, u32* ptr) { OSMemBlock* block; u32 max_free_block_size = 0; u32 free_blocks_size = 0; u32 free_blocks = 0; u32 used_blocks_size = 0; u32 used_blocks = 0; if (arena == NULL || ptr == NULL) { return -1; } arena_lock(arena); for (block = arena->head; block != NULL; block = get_block_next(block)) { if (block->free) { free_blocks++; free_blocks_size += block->size; if (max_free_block_size < block->size) { max_free_block_size = block->size; } } else { used_blocks++; used_blocks_size += block->size; } } ptr[0] = max_free_block_size; ptr[1] = free_blocks_size; ptr[2] = free_blocks; ptr[3] = used_blocks_size; ptr[4] = used_blocks; arena_unlock(arena); return 0; } extern void __osGetFreeArena(OSArena* arena, u32* max_free_block_size, u32* free_blocks_size, u32* used_blocks_size) { u32 data[5]; if (__osAnalyzeArena(arena, data) == 0) { if (max_free_block_size != NULL) { *max_free_block_size = data[0]; } if (free_blocks_size != NULL) { *free_blocks_size = data[1]; } if (used_blocks_size != NULL) { *used_blocks_size = data[3]; } } } extern u32 __osGetTotalFreeSize(OSArena* arena) { u32 total_free_size; __osGetFreeArena(arena, NULL, &total_free_size, NULL); return total_free_size; } #pragma force_active on extern u32 __osGetFreeSize(OSArena* arena) { u32 free_size; __osGetFreeArena(arena, &free_size, NULL, NULL); return free_size; } #pragma force_active reset extern s32 __osGetMemBlockSize(OSArena* arena, void* ptr) { OSMemBlock* block; if (ptr == NULL) { return -1; } block = OS_MALLOC_DATA2BLOCK(ptr); if (OS_MALLOC_BLOCK_OK(block)) { return block->size; } return -1; } extern void __osDisplayArena(OSArena* arena) { OSMemBlock* block; OSMemBlock* next; u32 max_free; u32 total_free; u32 total_used; if (__osMallocIsInitalized(arena)) { arena_lock(arena); max_free = 0; total_free = 0; total_used = 0; // Arena Contents OSReport("アリーナの内容 (0x%08x)\n", arena); // Memory block span | status | size | [time s ms us ns: TID:src:line] OSReport("メモリブロック範囲 status サイズ [時刻 s ms us ns: TID:src:行]\n"); block = arena->head; while (block != NULL) { if (OS_MALLOC_BLOCK_OK(block)) { next = block->next; OSReport("%08x-%08x%c %s %08x", (u32)block, (u32)block + sizeof(OSMemBlock) + block->size, next == NULL ? '$' : (next->prev != block ? '!' : ' '), block->free ? "空き" : "確保", block->size); if (!block->free) { // 空き = free, 確保 = used OSReport(" [%016llu:%2d:%s:%d]", OSTicksToMicroseconds((u64)block->time * 1000), block->threadId, block->file != NULL ? block->file : "**NULL**", block->line); } OSReport("\n"); if (block->free) { total_free += block->size; if (max_free < block->size) { max_free = block->size; } } else { total_used += block->size; } } else { OSReport("%08x Block Invalid\n", block); next = NULL; } block = next; } // Total used memblock size: 0x%08x bytes OSReport("確保ブロックサイズの合計 0x%08x バイト\n", total_used); // Total free memblock size: 0x%08x bytes OSReport("空きブロックサイズの合計 0x%08x バイト\n", total_free); // Largest free block size: %08x bytes OSReport("最大空きブロックサイズ 0x%08x バイト\n", max_free); arena_unlock(arena); } } #pragma force_active on extern int __osCheckArena(OSArena* arena) { int ret = FALSE; OSMemBlock* block; arena_lock(arena); block = arena->head; while (block != NULL) { if (!OS_MALLOC_BLOCK_OK(block)) { // Oops!! OSReport(VT_COL(RED, WHITE) "おおっと!! (%08x %08x)\n" VT_RST, block, block->magic); OSPanic(__FILE__, 1307, ""); ret = TRUE; break; } block = get_block_next(block); } arena_unlock(arena); return ret; } #pragma force_active reset