/* * This file is part of open_agb_firm * Copyright (C) 2023 derrek, profi200 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include "types.h" #include "drivers/pxi.h" #include "ipc_handler.h" #include "arm9/drivers/interrupt.h" // We are violating our IPC protocol sending data silently // but this is ok because for exceptions we have no other choice. static inline void sendRawPxiWord(const u32 data) { Pxi *const pxi = getPxiRegs(); while(pxi->cnt & PXI_CNT_SEND_FULL); pxi->send = data; } static inline void sendSyncRequest(void) { Pxi *const pxi = getPxiRegs(); pxi->sync_irq = PXI_SYNC_IRQ_IRQ_EN | PXI_SYNC_IRQ_IRQ; } static void sendFatalIpcCmd(const u32 param) { sendRawPxiWord(IPC_CMD11_A9_FATAL); sendSyncRequest(); sendRawPxiWord(param); } // Speed is not important. We need to be able to send data // even from unaligned addresses which is why this reads in bytes. static void sendRawPxiData(const void *data, u32 size) { const u8 *ptr8 = data; while(size > 0) { const u32 blockSize = (size > 4 ? 4 : size); u32 data = 0; for(u32 i = 0; i < blockSize; i++) { // Construct little endian words from the data. data |= ((u32)*ptr8++)<<(i * 8); } sendRawPxiWord(data); size -= blockSize; } } static void prepareExceptionHandling(void) { // Disable interrupts. Only required for assert()/panicMsg()/panic(). __setCpsr_c(__getCpsr() | PSR_I); // Block any attempts to run exception handling more than once. // Relaxed load/store + signal fence prevents atomic library calls. // Good enough for the single core ARM9. static atomic_bool exceptionLock = false; if(atomic_load_explicit(&exceptionLock, memory_order_relaxed)) { while(1) __wfi(); } atomic_store_explicit(&exceptionLock, true, memory_order_relaxed); atomic_signal_fence(memory_order_acquire); } [[noreturn]] NOINLINE void __fb_assert(const char *const file, const unsigned line, const char *const cond) { prepareExceptionHandling(); // Transfer fatal type and file string. sendFatalIpcCmd(0); if(file != NULL) { sendRawPxiData(file, strlen(file) + 1); } else { // Nothing to send. sendRawPxiWord(0); } // Transfer line number and failed condition string. sendRawPxiWord(line); if(cond != NULL) { sendRawPxiData(cond, strlen(cond) + 1); } else { // Nothing to send. sendRawPxiWord(0); } // Wait for power off. while(1) __wfi(); } [[noreturn]] NOINLINE void panicMsg(const char *const msg) { prepareExceptionHandling(); // Transfer fatal type and panic string. sendFatalIpcCmd(1); if(msg != NULL) { sendRawPxiData(msg, strlen(msg) + 1); } else { // Nothing to send. sendRawPxiWord(0); } // Wait for power off. while(1) __wfi(); } [[noreturn]] NOINLINE void panic(void) { panicMsg(NULL); } // Expects the registers on the exception stack to be in the following order: // r0-r14, pc (unmodified), cpsr. [[noreturn]] NOINLINE void guruMeditation(const u32 type, const u32 *excFrame) { prepareExceptionHandling(); // Fatal type exception. Exception type in bit 8-15. sendFatalIpcCmd(type<<8 | 2); // Transfer register dump. sendRawPxiData(excFrame, 4 * (16 + 1)); // r0-r15 and CPSR. // Transfer stack dump. const u32 sp = excFrame[13]; if(sp >= DTCM_BASE && sp < DTCM_BASE + DTCM_SIZE && (sp % 4) == 0) // TODO: Allow any valid memory region. { u32 stackWords = (DTCM_BASE + DTCM_SIZE - sp) / 4; stackWords = (stackWords > 96 ? 96 : stackWords); sendRawPxiWord(stackWords); for(u32 i = 0; i < stackWords; i++) { sendRawPxiWord(((u32*)sp)[i]); } } else { // No stack data to send. sendRawPxiWord(0); } // Wait for power off. while(1) __wfi(); } // ---------------------------------------------------------------- // #ifndef NDEBUG // Needs to be marked as used to work with LTO. // The used attribute also overrides the newlib symbol. // This is for debugging purposes only. For security this value needs to be random! __attribute__((used)) uintptr_t __stack_chk_guard = 0x8F303A48; // Needs to be marked as noinline and used to work with LTO. // The used attribute also overrides the newlib symbol. // Combine -fstack-protector-all with -fno-inline to get the most effective detection. [[noreturn]] __attribute__((noinline, used)) void __stack_chk_fail(void) { panicMsg("Stack smash!"); } // Add "-Wl,-wrap=malloc,-wrap=calloc,-wrap=free" to LDFLAGS to enable the heap check. static const u32 __heap_chk_guard[4] = {0x2241FCFC, 0x29EBBE29, 0x93EDFD9B, 0x141EF827}; void* __real_malloc(size_t size); void __real_free(void *ptr); void* __wrap_malloc(size_t size) { // Check if size + guard data overflows. size_t guardedSize; const size_t guardSize = sizeof(__heap_chk_guard); if(__builtin_add_overflow(size, guardSize * 2, &guardedSize)) return NULL; // Allocate memory large enough to hold size + guard data. u8 *const buf = __real_malloc(guardedSize); if(buf == NULL) return NULL; // Copy guard data at start and end of buffer. memcpy(buf, &size, sizeof(size_t)); memcpy(buf + sizeof(size_t), (u8*)__heap_chk_guard + sizeof(size_t), guardSize - sizeof(size_t)); memcpy(buf + guardSize + size, __heap_chk_guard, guardSize); return buf + guardSize; } void* __wrap_calloc(size_t num, size_t size) { // Check if num * size overflows. size_t allocSize; if(__builtin_mul_overflow(num, size, &allocSize)) return NULL; // Allocate buffer. void *const buf = __wrap_malloc(allocSize); if(buf == NULL) return NULL; // Clear buffer. memset(buf, 0, allocSize); return buf; } void __wrap_free(void *ptr) { // Check for NULL. u8 *const ptr8 = ptr; if(ptr8 == NULL) return; // Check if guard data at buffer start got overwritten. const size_t guardSize = sizeof(__heap_chk_guard); if(memcmp(ptr8 - (guardSize - sizeof(size_t)), (u8*)__heap_chk_guard + sizeof(size_t), guardSize - sizeof(size_t)) != 0) panicMsg("Heap underflow!"); // Copy the stored real buffer size. size_t size; memcpy(&size, ptr8 - guardSize, sizeof(size_t)); // Check if the buffer size makes sense. // Important! Adjust the size check if needed. // 1024u * 1024 is roughly ok for ARM9 mem. if(size > (1024u * 1024) || memcmp(ptr8 + size, __heap_chk_guard, guardSize) != 0) panicMsg("Heap overflow!"); __real_free(ptr8 - guardSize); } #endif