2101 lines
78 KiB
C
2101 lines
78 KiB
C
|
/***************************************************************
|
||
|
debug.c
|
||
|
|
||
|
A basic debug library that makes use of the USB library for N64
|
||
|
flashcarts.
|
||
|
https://github.com/buu342/N64-UNFLoader
|
||
|
***************************************************************/
|
||
|
|
||
|
#include "debug.h"
|
||
|
#ifndef LIBDRAGON
|
||
|
#include <ultra64.h>
|
||
|
#include <PR/os_internal.h> // Needed for Crash's Linux toolchain
|
||
|
#else
|
||
|
#include <libdragon.h>
|
||
|
#include <stdio.h>
|
||
|
#endif
|
||
|
#include <math.h>
|
||
|
#include <stdarg.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#if DEBUG_MODE
|
||
|
|
||
|
/*********************************
|
||
|
Definitions
|
||
|
*********************************/
|
||
|
|
||
|
// USB thread messages
|
||
|
#define MSG_FAULT 0x10
|
||
|
#define MSG_READ 0x11
|
||
|
#define MSG_WRITE 0x12
|
||
|
|
||
|
#define USBERROR_NONE 0
|
||
|
#define USBERROR_NOTTEXT 1
|
||
|
#define USBERROR_UNKNOWN 2
|
||
|
#define USBERROR_TOOMUCH 3
|
||
|
#define USBERROR_CUSTOM 4
|
||
|
|
||
|
// RDB thread messages (Libultra)
|
||
|
#ifndef LIBDRAGON
|
||
|
#define MSG_RDB_PACKET 0x10
|
||
|
#define MSG_RDB_BPHIT 0x11
|
||
|
#define MSG_RDB_PAUSE 0x12
|
||
|
#endif
|
||
|
|
||
|
// Breakpoints
|
||
|
#define BPOINT_COUNT 10
|
||
|
#define MAKE_BREAKPOINT_INDEX(indx) (0x0000000D | ((indx) << 6))
|
||
|
#define GET_BREAKPOINT_INDEX(addr) ((((addr) >> 6) & 0x0000FFFF))
|
||
|
|
||
|
// Helpful stuff
|
||
|
#define HASHTABLE_SIZE 7
|
||
|
#define COMMAND_TOKENS 10
|
||
|
#define BUFFER_SIZE 256
|
||
|
#define REGISTER_COUNT 72 // 32 GPRs + 6 SPRs + 16 FPRs + fsr + fir (fcr0)
|
||
|
#define REGISTER_SIZE 16 // GDB expects the registers to be 64-bits
|
||
|
|
||
|
|
||
|
/*********************************
|
||
|
Libultra types (for libdragon)
|
||
|
*********************************/
|
||
|
|
||
|
#ifdef LIBDRAGON
|
||
|
#ifndef TRUE
|
||
|
#define TRUE 1
|
||
|
#endif
|
||
|
#ifndef FALSE
|
||
|
#define FALSE 0
|
||
|
#endif
|
||
|
#define OS_PHYSICAL_TO_K0(x) (void *)(((u32)(x)+0x80000000))
|
||
|
#define OS_PHYSICAL_TO_K1(x) (void *)(((u32)(x)+0xa0000000))
|
||
|
|
||
|
typedef unsigned char u8;
|
||
|
typedef unsigned short u16;
|
||
|
typedef unsigned long u32;
|
||
|
typedef unsigned long long u64;
|
||
|
|
||
|
typedef signed char s8;
|
||
|
typedef short s16;
|
||
|
typedef long s32;
|
||
|
typedef long long s64;
|
||
|
|
||
|
typedef volatile unsigned char vu8;
|
||
|
typedef volatile unsigned short vu16;
|
||
|
typedef volatile unsigned long vu32;
|
||
|
typedef volatile unsigned long long vu64;
|
||
|
|
||
|
typedef volatile signed char vs8;
|
||
|
typedef volatile short vs16;
|
||
|
typedef volatile long vs32;
|
||
|
typedef volatile long long vs64;
|
||
|
|
||
|
typedef float f32;
|
||
|
typedef double f64;
|
||
|
|
||
|
typedef void* OSMesg;
|
||
|
typedef exception_t OSThread;
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/*********************************
|
||
|
Structs
|
||
|
*********************************/
|
||
|
|
||
|
// Register struct
|
||
|
typedef struct
|
||
|
{
|
||
|
u32 mask;
|
||
|
u32 value;
|
||
|
char *string;
|
||
|
} regDesc;
|
||
|
|
||
|
// Because of the thread context's messy struct, this'll come in handy
|
||
|
typedef struct {
|
||
|
int size;
|
||
|
void* ptr;
|
||
|
} regType;
|
||
|
|
||
|
// Thread message struct
|
||
|
typedef struct
|
||
|
{
|
||
|
int msgtype;
|
||
|
int datatype;
|
||
|
void* buff;
|
||
|
int size;
|
||
|
} usbMesg;
|
||
|
|
||
|
// Debug command struct
|
||
|
typedef struct
|
||
|
{
|
||
|
char* command;
|
||
|
char* description;
|
||
|
char* (*execute)();
|
||
|
void* next;
|
||
|
} debugCommand;
|
||
|
|
||
|
// Remote debugger packet lookup table
|
||
|
typedef struct
|
||
|
{
|
||
|
char* command;
|
||
|
void (*func)();
|
||
|
} RDBPacketLUT;
|
||
|
|
||
|
// Breakpoint struct
|
||
|
typedef struct
|
||
|
{
|
||
|
u32* addr;
|
||
|
u32 instruction;
|
||
|
} bPoint;
|
||
|
|
||
|
|
||
|
/*********************************
|
||
|
Function Prototypes
|
||
|
*********************************/
|
||
|
|
||
|
// Threads
|
||
|
static void debug_thread_usb(void* arg);
|
||
|
#ifndef LIBDRAGON
|
||
|
#if USE_FAULTTHREAD
|
||
|
static void debug_thread_fault(void* arg);
|
||
|
#endif
|
||
|
#else
|
||
|
#if AUTOPOLL_ENABLED
|
||
|
static void debug_timer_usb(int overflow);
|
||
|
#endif
|
||
|
#endif
|
||
|
#if USE_RDBTHREAD
|
||
|
#ifndef LIBDRAGON
|
||
|
static void debug_thread_rdb(void* arg);
|
||
|
#else
|
||
|
static void debug_thread_rdb(exception_t* arg);
|
||
|
static void debug_thread_rdb_pause();
|
||
|
#endif
|
||
|
static void debug_thread_rdb_loop(OSThread* t);
|
||
|
static void debug_rdb_qsupported(OSThread* t);
|
||
|
static void debug_rdb_haltreason(OSThread* t);
|
||
|
static void debug_rdb_dumpregisters(OSThread* t);
|
||
|
static void debug_rdb_writeregisters(OSThread* t);
|
||
|
static void debug_rdb_readmemory(OSThread* t);
|
||
|
static void debug_rdb_writememory(OSThread* t);
|
||
|
static void debug_rdb_addbreakpoint(OSThread* t);
|
||
|
static void debug_rdb_removebreakpoint(OSThread* t);
|
||
|
static void debug_rdb_continue(OSThread* t);
|
||
|
static void debug_rdb_pause(OSThread* t);
|
||
|
#endif
|
||
|
|
||
|
// Other
|
||
|
#ifndef LIBDRAGON
|
||
|
#if OVERWRITE_OSPRINT
|
||
|
static void* debug_osSyncPrintf_implementation(void *unused, const char *str, size_t len);
|
||
|
#endif
|
||
|
#endif
|
||
|
static inline void debug_handle_64drivebutton();
|
||
|
|
||
|
|
||
|
/*********************************
|
||
|
Globals
|
||
|
*********************************/
|
||
|
|
||
|
// Function pointers
|
||
|
#ifndef LIBDRAGON
|
||
|
extern int _Printf(void *(*copyfunc)(void *, const char *, size_t), void*, const char*, va_list);
|
||
|
#if OVERWRITE_OSPRINT
|
||
|
extern void* __printfunc;
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
// Debug globals
|
||
|
static char debug_initialized = 0;
|
||
|
static char debug_buffer[BUFFER_SIZE];
|
||
|
|
||
|
// Commands hashtable related
|
||
|
static debugCommand* debug_commands_hashtable[HASHTABLE_SIZE];
|
||
|
static debugCommand debug_commands_elements[MAX_COMMANDS];
|
||
|
static int debug_commands_count = 0;
|
||
|
|
||
|
// Command parsing related
|
||
|
static int debug_command_current = 0;
|
||
|
static int debug_command_totaltokens = 0;
|
||
|
static int debug_command_incoming_start[COMMAND_TOKENS];
|
||
|
static int debug_command_incoming_size[COMMAND_TOKENS];
|
||
|
static char* debug_command_error = NULL;
|
||
|
|
||
|
// Assertion globals
|
||
|
static int assert_line = 0;
|
||
|
static const char* assert_file = NULL;
|
||
|
static const char* assert_expr = NULL;
|
||
|
|
||
|
// 64Drive button functions
|
||
|
static void (*debug_64dbut_func)() = NULL;
|
||
|
static u64 debug_64dbut_debounce = 0;
|
||
|
static u64 debug_64dbut_hold = 0;
|
||
|
|
||
|
#ifndef LIBDRAGON
|
||
|
|
||
|
// USB thread globals
|
||
|
static OSMesgQueue usbMessageQ;
|
||
|
static OSMesg usbMessageBuf;
|
||
|
static OSThread usbThread;
|
||
|
static u64 usbThreadStack[USB_THREAD_STACK/sizeof(u64)];
|
||
|
#if AUTOPOLL_ENABLED
|
||
|
static OSTimer usbThreadTimer;
|
||
|
#endif
|
||
|
|
||
|
// Fault thread globals
|
||
|
#if USE_FAULTTHREAD
|
||
|
static OSMesgQueue faultMessageQ;
|
||
|
static OSMesg faultMessageBuf;
|
||
|
static OSThread faultThread;
|
||
|
static u64 faultThreadStack[FAULT_THREAD_STACK/sizeof(u64)];
|
||
|
|
||
|
// List of error causes
|
||
|
static regDesc causeDesc[] = {
|
||
|
{CAUSE_BD, CAUSE_BD, "BD"},
|
||
|
{CAUSE_IP8, CAUSE_IP8, "IP8"},
|
||
|
{CAUSE_IP7, CAUSE_IP7, "IP7"},
|
||
|
{CAUSE_IP6, CAUSE_IP6, "IP6"},
|
||
|
{CAUSE_IP5, CAUSE_IP5, "IP5"},
|
||
|
{CAUSE_IP4, CAUSE_IP4, "IP4"},
|
||
|
{CAUSE_IP3, CAUSE_IP3, "IP3"},
|
||
|
{CAUSE_SW2, CAUSE_SW2, "IP2"},
|
||
|
{CAUSE_SW1, CAUSE_SW1, "IP1"},
|
||
|
{CAUSE_EXCMASK, EXC_INT, "Interrupt"},
|
||
|
{CAUSE_EXCMASK, EXC_MOD, "TLB modification exception"},
|
||
|
{CAUSE_EXCMASK, EXC_RMISS, "TLB exception on load or instruction fetch"},
|
||
|
{CAUSE_EXCMASK, EXC_WMISS, "TLB exception on store"},
|
||
|
{CAUSE_EXCMASK, EXC_RADE, "Address error on load or instruction fetch"},
|
||
|
{CAUSE_EXCMASK, EXC_WADE, "Address error on store"},
|
||
|
{CAUSE_EXCMASK, EXC_IBE, "Bus error exception on instruction fetch"},
|
||
|
{CAUSE_EXCMASK, EXC_DBE, "Bus error exception on data reference"},
|
||
|
{CAUSE_EXCMASK, EXC_SYSCALL, "System call exception"},
|
||
|
{CAUSE_EXCMASK, EXC_BREAK, "Breakpoint exception"},
|
||
|
{CAUSE_EXCMASK, EXC_II, "Reserved instruction exception"},
|
||
|
{CAUSE_EXCMASK, EXC_CPU, "Coprocessor unusable exception"},
|
||
|
{CAUSE_EXCMASK, EXC_OV, "Arithmetic overflow exception"},
|
||
|
{CAUSE_EXCMASK, EXC_TRAP, "Trap exception"},
|
||
|
{CAUSE_EXCMASK, EXC_VCEI, "Virtual coherency exception on intruction fetch"},
|
||
|
{CAUSE_EXCMASK, EXC_FPE, "Floating point exception (see fpcsr)"},
|
||
|
{CAUSE_EXCMASK, EXC_WATCH, "Watchpoint exception"},
|
||
|
{CAUSE_EXCMASK, EXC_VCED, "Virtual coherency exception on data reference"},
|
||
|
{0, 0, ""}
|
||
|
};
|
||
|
|
||
|
// List of register descriptions
|
||
|
static regDesc srDesc[] = {
|
||
|
{SR_CU3, SR_CU3, "CU3"},
|
||
|
{SR_CU2, SR_CU2, "CU2"},
|
||
|
{SR_CU1, SR_CU1, "CU1"},
|
||
|
{SR_CU0, SR_CU0, "CU0"},
|
||
|
{SR_RP, SR_RP, "RP"},
|
||
|
{SR_FR, SR_FR, "FR"},
|
||
|
{SR_RE, SR_RE, "RE"},
|
||
|
{SR_BEV, SR_BEV, "BEV"},
|
||
|
{SR_TS, SR_TS, "TS"},
|
||
|
{SR_SR, SR_SR, "SR"},
|
||
|
{SR_CH, SR_CH, "CH"},
|
||
|
{SR_CE, SR_CE, "CE"},
|
||
|
{SR_DE, SR_DE, "DE"},
|
||
|
{SR_IBIT8, SR_IBIT8, "IM8"},
|
||
|
{SR_IBIT7, SR_IBIT7, "IM7"},
|
||
|
{SR_IBIT6, SR_IBIT6, "IM6"},
|
||
|
{SR_IBIT5, SR_IBIT5, "IM5"},
|
||
|
{SR_IBIT4, SR_IBIT4, "IM4"},
|
||
|
{SR_IBIT3, SR_IBIT3, "IM3"},
|
||
|
{SR_IBIT2, SR_IBIT2, "IM2"},
|
||
|
{SR_IBIT1, SR_IBIT1, "IM1"},
|
||
|
{SR_KX, SR_KX, "KX"},
|
||
|
{SR_SX, SR_SX, "SX"},
|
||
|
{SR_UX, SR_UX, "UX"},
|
||
|
{SR_KSU_MASK, SR_KSU_USR, "USR"},
|
||
|
{SR_KSU_MASK, SR_KSU_SUP, "SUP"},
|
||
|
{SR_KSU_MASK, SR_KSU_KER, "KER"},
|
||
|
{SR_ERL, SR_ERL, "ERL"},
|
||
|
{SR_EXL, SR_EXL, "EXL"},
|
||
|
{SR_IE, SR_IE, "IE"},
|
||
|
{0, 0, ""}
|
||
|
};
|
||
|
|
||
|
// List of floating point registers descriptions
|
||
|
static regDesc fpcsrDesc[] = {
|
||
|
{FPCSR_FS, FPCSR_FS, "FS"},
|
||
|
{FPCSR_C, FPCSR_C, "C"},
|
||
|
{FPCSR_CE, FPCSR_CE, "Unimplemented operation"},
|
||
|
{FPCSR_CV, FPCSR_CV, "Invalid operation"},
|
||
|
{FPCSR_CZ, FPCSR_CZ, "Division by zero"},
|
||
|
{FPCSR_CO, FPCSR_CO, "Overflow"},
|
||
|
{FPCSR_CU, FPCSR_CU, "Underflow"},
|
||
|
{FPCSR_CI, FPCSR_CI, "Inexact operation"},
|
||
|
{FPCSR_EV, FPCSR_EV, "EV"},
|
||
|
{FPCSR_EZ, FPCSR_EZ, "EZ"},
|
||
|
{FPCSR_EO, FPCSR_EO, "EO"},
|
||
|
{FPCSR_EU, FPCSR_EU, "EU"},
|
||
|
{FPCSR_EI, FPCSR_EI, "EI"},
|
||
|
{FPCSR_FV, FPCSR_FV, "FV"},
|
||
|
{FPCSR_FZ, FPCSR_FZ, "FZ"},
|
||
|
{FPCSR_FO, FPCSR_FO, "FO"},
|
||
|
{FPCSR_FU, FPCSR_FU, "FU"},
|
||
|
{FPCSR_FI, FPCSR_FI, "FI"},
|
||
|
{FPCSR_RM_MASK, FPCSR_RM_RN, "RN"},
|
||
|
{FPCSR_RM_MASK, FPCSR_RM_RZ, "RZ"},
|
||
|
{FPCSR_RM_MASK, FPCSR_RM_RP, "RP"},
|
||
|
{FPCSR_RM_MASK, FPCSR_RM_RM, "RM"},
|
||
|
{0, 0, ""}
|
||
|
};
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
#if USE_RDBTHREAD
|
||
|
// Remote debugger thread globals
|
||
|
#ifndef LIBDRAGON
|
||
|
static OSMesgQueue rdbMessageQ;
|
||
|
static OSMesg rdbMessageBuf;
|
||
|
static OSThread rdbThread;
|
||
|
static u64 rdbThreadStack[RDB_THREAD_STACK/sizeof(u64)];
|
||
|
#endif
|
||
|
|
||
|
// RDB status globals
|
||
|
static vu8 debug_rdbpaused = FALSE;
|
||
|
#ifndef LIBDRAGON
|
||
|
static OSTime debug_pausetime = 0;
|
||
|
#else
|
||
|
static u32 debug_pausetime = 0;
|
||
|
static u8 debug_ismanualpause = FALSE;
|
||
|
#endif
|
||
|
static bPoint debug_bpoints[BPOINT_COUNT];
|
||
|
|
||
|
// Remote debugger packet lookup table
|
||
|
RDBPacketLUT lut_rdbpackets[] = {
|
||
|
// Due to the use of strncmp, the order of strings matters!
|
||
|
{"qSupported", debug_rdb_qsupported},
|
||
|
{"?", debug_rdb_haltreason},
|
||
|
{"g", debug_rdb_dumpregisters},
|
||
|
{"G", debug_rdb_writeregisters},
|
||
|
{"m", debug_rdb_readmemory},
|
||
|
{"M", debug_rdb_writememory},
|
||
|
{"Z0", debug_rdb_addbreakpoint},
|
||
|
{"z0", debug_rdb_removebreakpoint},
|
||
|
{"c", debug_rdb_continue},
|
||
|
{"\x03", debug_rdb_pause},
|
||
|
};
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/*********************************
|
||
|
Debug functions
|
||
|
*********************************/
|
||
|
|
||
|
/*==============================
|
||
|
debug_initialize
|
||
|
Initializes the debug library
|
||
|
==============================*/
|
||
|
|
||
|
void debug_initialize()
|
||
|
{
|
||
|
// Initialize the USB functions
|
||
|
if (!usb_initialize())
|
||
|
return;
|
||
|
|
||
|
// Initialize globals
|
||
|
memset(debug_commands_hashtable, 0, sizeof(debugCommand*)*HASHTABLE_SIZE);
|
||
|
memset(debug_commands_elements, 0, sizeof(debugCommand)*MAX_COMMANDS);
|
||
|
|
||
|
// Libultra functions
|
||
|
#ifndef LIBDRAGON
|
||
|
// Overwrite osSyncPrintf
|
||
|
#if OVERWRITE_OSPRINT
|
||
|
__printfunc = (void*)debug_osSyncPrintf_implementation;
|
||
|
#endif
|
||
|
|
||
|
// Initialize the USB thread
|
||
|
osCreateThread(&usbThread, USB_THREAD_ID, debug_thread_usb, 0,
|
||
|
(usbThreadStack+USB_THREAD_STACK/sizeof(u64)),
|
||
|
USB_THREAD_PRI);
|
||
|
osStartThread(&usbThread);
|
||
|
#if AUTOPOLL_ENABLED
|
||
|
osSetTimer(&usbThreadTimer, 0, OS_USEC_TO_CYCLES(AUTOPOLL_TIME*1000), &usbMessageQ, (OSMesg)NULL);
|
||
|
#endif
|
||
|
|
||
|
// Initialize the fault thread
|
||
|
#if USE_FAULTTHREAD
|
||
|
osCreateThread(&faultThread, FAULT_THREAD_ID, debug_thread_fault, 0,
|
||
|
(faultThreadStack+FAULT_THREAD_STACK/sizeof(u64)),
|
||
|
FAULT_THREAD_PRI);
|
||
|
osStartThread(&faultThread);
|
||
|
#endif
|
||
|
|
||
|
// Initialize the remote debugger thread
|
||
|
#if USE_RDBTHREAD
|
||
|
osCreateThread(&rdbThread, RDB_THREAD_ID, debug_thread_rdb, (void*)osGetThreadId(NULL),
|
||
|
(rdbThreadStack+RDB_THREAD_STACK/sizeof(u64)),
|
||
|
RDB_THREAD_PRI);
|
||
|
osStartThread(&rdbThread);
|
||
|
|
||
|
// Initialize breakpoints
|
||
|
osSetEventMesg(OS_EVENT_CPU_BREAK, &rdbMessageQ, (OSMesg)MSG_RDB_BPHIT);
|
||
|
memset(debug_bpoints, 0, BPOINT_COUNT*sizeof(bPoint));
|
||
|
|
||
|
// Pause the main thread
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_TEXT, "Pausing main thread until GDB connects and resumes\n", 51+1);
|
||
|
osSendMesg(&rdbMessageQ, (OSMesg)MSG_RDB_PAUSE, OS_MESG_BLOCK);
|
||
|
#endif
|
||
|
#else
|
||
|
timer_init(); // If the timer subsystem has been initialized already, it's not a problem to call it again.
|
||
|
#if AUTOPOLL_ENABLED
|
||
|
new_timer(TIMER_TICKS(AUTOPOLL_TIME*1000), TF_CONTINUOUS, debug_timer_usb);
|
||
|
#endif
|
||
|
#if USE_RDBTHREAD
|
||
|
memset(debug_bpoints, 0, BPOINT_COUNT*sizeof(bPoint));
|
||
|
register_exception_handler(debug_thread_rdb);
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_TEXT, "Pausing main thread until GDB connects and resumes\n", 51+1);
|
||
|
debug_thread_rdb_pause();
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
// Mark the debug mode as initialized
|
||
|
debug_initialized = 1;
|
||
|
#if DEBUG_INIT_MSG
|
||
|
debug_printf("Debug mode initialized!\n\n");
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
#ifndef LIBDRAGON
|
||
|
/*==============================
|
||
|
printf_handler
|
||
|
Handles printf memory copying
|
||
|
@param The buffer to copy the partial string to
|
||
|
@param The string to copy
|
||
|
@param The length of the string
|
||
|
@returns The end of the buffer that was written to
|
||
|
==============================*/
|
||
|
|
||
|
static void* printf_handler(void *buf, const char *str, size_t len)
|
||
|
{
|
||
|
return ((char *) memcpy(buf, str, len) + len);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_printf
|
||
|
Prints a formatted message to the developer's command prompt.
|
||
|
Supports up to 256 characters.
|
||
|
@param A string to print
|
||
|
@param variadic arguments to print as well
|
||
|
==============================*/
|
||
|
|
||
|
void debug_printf(const char* message, ...)
|
||
|
{
|
||
|
int len = 0;
|
||
|
usbMesg msg;
|
||
|
va_list args;
|
||
|
|
||
|
// Use the internal libultra printf function to format the string
|
||
|
va_start(args, message);
|
||
|
#ifndef LIBDRAGON
|
||
|
len = _Printf(&printf_handler, debug_buffer, message, args);
|
||
|
#else
|
||
|
len = vsprintf(debug_buffer, message, args);
|
||
|
#endif
|
||
|
va_end(args);
|
||
|
|
||
|
// Attach the '\0' if necessary
|
||
|
if (0 <= len)
|
||
|
debug_buffer[len] = '\0';
|
||
|
|
||
|
// Send the printf to the usb thread
|
||
|
msg.msgtype = MSG_WRITE;
|
||
|
msg.datatype = DATATYPE_TEXT;
|
||
|
msg.buff = debug_buffer;
|
||
|
msg.size = len+1;
|
||
|
#ifndef LIBDRAGON
|
||
|
osSendMesg(&usbMessageQ, (OSMesg)&msg, OS_MESG_BLOCK);
|
||
|
#else
|
||
|
debug_thread_usb(&msg);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_dumpbinary
|
||
|
Dumps a binary file through USB
|
||
|
@param The file to dump
|
||
|
@param The size of the file
|
||
|
==============================*/
|
||
|
|
||
|
void debug_dumpbinary(void* file, int size)
|
||
|
{
|
||
|
usbMesg msg;
|
||
|
|
||
|
// Send the binary file to the usb thread
|
||
|
msg.msgtype = MSG_WRITE;
|
||
|
msg.datatype = DATATYPE_RAWBINARY;
|
||
|
msg.buff = file;
|
||
|
msg.size = size;
|
||
|
#ifndef LIBDRAGON
|
||
|
osSendMesg(&usbMessageQ, (OSMesg)&msg, OS_MESG_BLOCK);
|
||
|
#else
|
||
|
debug_thread_usb(&msg);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_screenshot
|
||
|
Sends the currently displayed framebuffer through USB.
|
||
|
DOES NOT PAUSE DRAWING THREAD! Using outside the drawing
|
||
|
thread may lead to a screenshot with visible tearing
|
||
|
==============================*/
|
||
|
|
||
|
void debug_screenshot()
|
||
|
{
|
||
|
usbMesg msg;
|
||
|
int data[4];
|
||
|
|
||
|
// These addresses were obtained from http://en64.shoutwiki.com/wiki/VI_Registers_Detailed
|
||
|
void* frame = (void*)(0x80000000|(*(u32*)0xA4400004)); // Same as calling osViGetCurrentFramebuffer() in libultra
|
||
|
u32 yscale = (*(u32*)0xA4400034);
|
||
|
u32 w = (*(u32*)0xA4400008);
|
||
|
u32 h = ((((*(u32*)0xA4400028)&0x3FF)-(((*(u32*)0xA4400028)>>16)&0x3FF))*yscale)/2048;
|
||
|
u8 depth = (((*(u32*)0xA4400000)&0x03) == 0x03) ? 4 : 2;
|
||
|
|
||
|
// Ensure debug mode is initialized
|
||
|
if (!debug_initialized)
|
||
|
return;
|
||
|
|
||
|
// Create the data header to send
|
||
|
data[0] = DATATYPE_SCREENSHOT;
|
||
|
data[1] = depth;
|
||
|
data[2] = w;
|
||
|
data[3] = h;
|
||
|
|
||
|
// Send the header to the USB thread
|
||
|
msg.msgtype = MSG_WRITE;
|
||
|
msg.datatype = DATATYPE_HEADER;
|
||
|
msg.buff = data;
|
||
|
msg.size = sizeof(data);
|
||
|
#ifndef LIBDRAGON
|
||
|
osSendMesg(&usbMessageQ, (OSMesg)&msg, OS_MESG_BLOCK);
|
||
|
#else
|
||
|
debug_thread_usb(&msg);
|
||
|
#endif
|
||
|
|
||
|
// Send the framebuffer to the USB thread
|
||
|
msg.msgtype = MSG_WRITE;
|
||
|
msg.datatype = DATATYPE_SCREENSHOT;
|
||
|
msg.buff = frame;
|
||
|
msg.size = depth*w*h;
|
||
|
#ifndef LIBDRAGON
|
||
|
osSendMesg(&usbMessageQ, (OSMesg)&msg, OS_MESG_BLOCK);
|
||
|
#else
|
||
|
debug_thread_usb(&msg);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
_debug_assert
|
||
|
Halts the program (assumes expression failed)
|
||
|
@param The expression that was tested
|
||
|
@param The file where the exception ocurred
|
||
|
@param The line number where the exception ocurred
|
||
|
==============================*/
|
||
|
|
||
|
void _debug_assert(const char* expression, const char* file, int line)
|
||
|
{
|
||
|
volatile char crash;
|
||
|
|
||
|
// Set the assert data
|
||
|
assert_expr = expression;
|
||
|
assert_line = line;
|
||
|
assert_file = file;
|
||
|
|
||
|
// If on libdragon, print where the assertion failed
|
||
|
#ifdef LIBDRAGON
|
||
|
debug_printf("Assertion failed in file '%s', line %d.\n", assert_file, assert_line);
|
||
|
#endif
|
||
|
|
||
|
// Intentionally cause a TLB exception on load/instruction fetch
|
||
|
#ifdef LIBDRAGON
|
||
|
#pragma GCC diagnostic push
|
||
|
#pragma GCC diagnostic ignored "-Warray-bounds"
|
||
|
#endif
|
||
|
crash = *(volatile char *)1;
|
||
|
#ifdef LIBDRAGON
|
||
|
#pragma GCC diagnostic pop
|
||
|
#endif
|
||
|
(void)crash;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_64drivebutton
|
||
|
Assigns a function to be executed when the 64drive button is pressed.
|
||
|
@param The function pointer to execute
|
||
|
@param Whether or not to execute the function only on pressing (ignore holding the button down)
|
||
|
==============================*/
|
||
|
|
||
|
void debug_64drivebutton(void(*execute)(), char onpress)
|
||
|
{
|
||
|
debug_64dbut_func = execute;
|
||
|
debug_64dbut_debounce = 0;
|
||
|
debug_64dbut_hold = !onpress;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_addcommand
|
||
|
Adds a command for the USB to listen for
|
||
|
@param The command name
|
||
|
@param The command description
|
||
|
@param The function pointer to execute
|
||
|
==============================*/
|
||
|
|
||
|
void debug_addcommand(char* command, char* description, char* (*execute)())
|
||
|
{
|
||
|
int entry = command[0]%HASHTABLE_SIZE;
|
||
|
debugCommand* slot = debug_commands_hashtable[entry];
|
||
|
|
||
|
// Ensure debug mode is initialized
|
||
|
if (!debug_initialized)
|
||
|
return;
|
||
|
|
||
|
// Ensure we haven't hit the command limit
|
||
|
if (debug_commands_count == MAX_COMMANDS)
|
||
|
{
|
||
|
debug_printf("Max commands exceeded!\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Look for an empty spot in the hash table
|
||
|
if (slot != NULL)
|
||
|
{
|
||
|
while (slot->next != NULL)
|
||
|
slot = slot->next;
|
||
|
slot->next = &debug_commands_elements[debug_commands_count];
|
||
|
}
|
||
|
else
|
||
|
debug_commands_hashtable[entry] = &debug_commands_elements[debug_commands_count];
|
||
|
|
||
|
// Fill this spot with info about this command
|
||
|
debug_commands_elements[debug_commands_count].command = command;
|
||
|
debug_commands_elements[debug_commands_count].description = description;
|
||
|
debug_commands_elements[debug_commands_count].execute = execute;
|
||
|
debug_commands_count++;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_printcommands
|
||
|
Prints a list of commands to the developer's command prompt.
|
||
|
==============================*/
|
||
|
|
||
|
void debug_printcommands()
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
// Ensure debug mode is initialized
|
||
|
if (!debug_initialized)
|
||
|
return;
|
||
|
|
||
|
// Ensure there are commands to print
|
||
|
if (debug_commands_count == 0)
|
||
|
return;
|
||
|
|
||
|
// Print the commands
|
||
|
debug_printf("Available USB commands\n----------------------\n");
|
||
|
for (i=0; i<debug_commands_count; i++)
|
||
|
debug_printf("%d. %s\n\t%s\n", i+1, debug_commands_elements[i].command, debug_commands_elements[i].description);
|
||
|
debug_printf("\n");
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_pollcommands
|
||
|
Check the USB for incoming commands
|
||
|
==============================*/
|
||
|
|
||
|
void debug_pollcommands()
|
||
|
{
|
||
|
usbMesg msg;
|
||
|
|
||
|
// Ensure debug mode is initialized
|
||
|
if (!debug_initialized)
|
||
|
return;
|
||
|
|
||
|
// Send a read message to the USB thread
|
||
|
msg.msgtype = MSG_READ;
|
||
|
#ifndef LIBDRAGON
|
||
|
osSendMesg(&usbMessageQ, (OSMesg)&msg, OS_MESG_BLOCK);
|
||
|
#else
|
||
|
debug_thread_usb(&msg);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_handle_64drivebutton
|
||
|
Handles the 64Drive's button logic
|
||
|
==============================*/
|
||
|
|
||
|
static inline void debug_handle_64drivebutton()
|
||
|
{
|
||
|
static u32 held = 0;
|
||
|
|
||
|
// If we own a 64Drive
|
||
|
if (usb_getcart() == CART_64DRIVE && debug_64dbut_func != NULL)
|
||
|
{
|
||
|
u64 curtime;
|
||
|
#ifndef LIBDRAGON
|
||
|
curtime = osGetTime();
|
||
|
#else
|
||
|
curtime = timer_ticks();
|
||
|
#endif
|
||
|
|
||
|
// And the debounce time on the 64Drive's button has elapsed
|
||
|
if (debug_64dbut_debounce < curtime)
|
||
|
{
|
||
|
s32 bpoll;
|
||
|
#ifndef LIBDRAGON
|
||
|
osPiReadIo(0xB80002F8, &bpoll);
|
||
|
#else
|
||
|
bpoll = io_read(0xB80002F8);
|
||
|
#endif
|
||
|
bpoll = (bpoll&0xFFFF0000)>>16;
|
||
|
|
||
|
// If the 64Drive's button has been pressed, then execute the assigned function and set the debounce timer
|
||
|
if (bpoll == 0 && (debug_64dbut_hold || !held))
|
||
|
{
|
||
|
u64 nexttime;
|
||
|
#ifndef LIBDRAGON
|
||
|
nexttime = OS_USEC_TO_CYCLES(100000);
|
||
|
#else
|
||
|
nexttime = TIMER_TICKS(100000);
|
||
|
#endif
|
||
|
debug_64dbut_debounce = curtime + nexttime;
|
||
|
debug_64dbut_func();
|
||
|
held = 1;
|
||
|
}
|
||
|
else if (bpoll != 0 && held)
|
||
|
held = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_sizecommand
|
||
|
Returns the size of the data from this part of the command
|
||
|
@return The size of the data in bytes, or 0
|
||
|
==============================*/
|
||
|
|
||
|
int debug_sizecommand()
|
||
|
{
|
||
|
// If we're out of commands to read, return 0
|
||
|
if (debug_command_current == debug_command_totaltokens)
|
||
|
return 0;
|
||
|
|
||
|
// Otherwise, return the amount of data to read
|
||
|
return debug_command_incoming_size[debug_command_current];
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_parsecommand
|
||
|
Stores the next part of the incoming command into the provided buffer.
|
||
|
Make sure the buffer can fit the amount of data from debug_sizecommand!
|
||
|
If you pass NULL, it skips this command.
|
||
|
@param The buffer to store the data in
|
||
|
==============================*/
|
||
|
|
||
|
void debug_parsecommand(void* buffer)
|
||
|
{
|
||
|
u8 curr = debug_command_current;
|
||
|
|
||
|
// Skip this command if no buffer exists
|
||
|
if (buffer == NULL)
|
||
|
{
|
||
|
debug_command_current++;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// If we're out of commands to read, do nothing
|
||
|
if (curr == debug_command_totaltokens)
|
||
|
return;
|
||
|
|
||
|
// Read from the correct offset
|
||
|
usb_skip(debug_command_incoming_start[curr]);
|
||
|
usb_read(buffer, debug_command_incoming_size[curr]);
|
||
|
usb_rewind(debug_command_incoming_size[curr]+debug_command_incoming_start[curr]);
|
||
|
debug_command_current++;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_commands_setup
|
||
|
Reads the entire incoming string and breaks it into parts for
|
||
|
debug_parsecommand and debug_sizecommand
|
||
|
==============================*/
|
||
|
|
||
|
static void debug_commands_setup()
|
||
|
{
|
||
|
int i;
|
||
|
int datasize = USBHEADER_GETSIZE(usb_poll());
|
||
|
int dataleft = datasize;
|
||
|
int filesize = 0;
|
||
|
char filestep = 0;
|
||
|
|
||
|
// Initialize the starting offsets at -1
|
||
|
memset(debug_command_incoming_start, -1, COMMAND_TOKENS*sizeof(int));
|
||
|
|
||
|
// Read data from USB in blocks
|
||
|
while (dataleft > 0)
|
||
|
{
|
||
|
int readsize = BUFFER_SIZE;
|
||
|
if (readsize > dataleft)
|
||
|
readsize = dataleft;
|
||
|
|
||
|
// Read a block from USB
|
||
|
memset(debug_buffer, 0, BUFFER_SIZE);
|
||
|
usb_read(debug_buffer, readsize);
|
||
|
|
||
|
// Parse the block
|
||
|
for (i=0; i<readsize && dataleft > 0; i++)
|
||
|
{
|
||
|
// If we're not reading a file
|
||
|
int offset = datasize-dataleft;
|
||
|
u8 tok = debug_command_totaltokens;
|
||
|
|
||
|
// Decide what to do based on the current character
|
||
|
switch (debug_buffer[i])
|
||
|
{
|
||
|
case ' ':
|
||
|
case '\0':
|
||
|
if (filestep < 2)
|
||
|
{
|
||
|
if (debug_command_incoming_start[tok] != -1)
|
||
|
{
|
||
|
debug_command_incoming_size[tok] = offset-debug_command_incoming_start[tok];
|
||
|
debug_command_totaltokens++;
|
||
|
}
|
||
|
|
||
|
if (debug_buffer[i] == '\0')
|
||
|
dataleft = 0;
|
||
|
break;
|
||
|
}
|
||
|
case '@':
|
||
|
filestep++;
|
||
|
if (filestep < 3)
|
||
|
break;
|
||
|
default:
|
||
|
// Decide what to do based on the file handle
|
||
|
if (filestep == 0 && debug_command_incoming_start[tok] == -1)
|
||
|
{
|
||
|
// Store the data offsets and sizes in the global command buffers
|
||
|
debug_command_incoming_start[tok] = offset;
|
||
|
}
|
||
|
else if (filestep == 1)
|
||
|
{
|
||
|
// Get the filesize
|
||
|
filesize = filesize*10 + debug_buffer[i]-'0';
|
||
|
}
|
||
|
else if (filestep > 1)
|
||
|
{
|
||
|
// Store the file offsets and sizes in the global command buffers
|
||
|
debug_command_incoming_start[tok] = offset;
|
||
|
debug_command_incoming_size[tok] = filesize;
|
||
|
debug_command_totaltokens++;
|
||
|
|
||
|
// Skip a bunch of bytes
|
||
|
if ((readsize-i)-filesize < 0)
|
||
|
usb_skip(filesize-(readsize-i));
|
||
|
dataleft -= filesize;
|
||
|
i += filesize;
|
||
|
filesize = 0;
|
||
|
filestep = 0;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
dataleft--;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Rewind the USB fully
|
||
|
usb_rewind(datasize);
|
||
|
}
|
||
|
|
||
|
#ifdef LIBDRAGON
|
||
|
#if AUTOPOLL_ENABLED
|
||
|
/*==============================
|
||
|
debug_timer_usb
|
||
|
A function that's called by the auto-poll timer
|
||
|
@param How many ticks the timer overflew by (unused)
|
||
|
==============================*/
|
||
|
|
||
|
static void debug_timer_usb(int overflow)
|
||
|
{
|
||
|
usbMesg msg;
|
||
|
(void)overflow; // To prevent unused variable errors
|
||
|
msg.msgtype = MSG_READ;
|
||
|
debug_thread_usb(&msg);
|
||
|
}
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_thread_usb
|
||
|
Handles the USB thread
|
||
|
@param Arbitrary data that the thread can receive
|
||
|
==============================*/
|
||
|
|
||
|
static void debug_thread_usb(void *arg)
|
||
|
{
|
||
|
char errortype = USBERROR_NONE;
|
||
|
usbMesg* threadMsg;
|
||
|
|
||
|
#ifndef LIBDRAGON
|
||
|
// Create the message queue for the USB message
|
||
|
osCreateMesgQueue(&usbMessageQ, &usbMessageBuf, 1);
|
||
|
#else
|
||
|
// Set the received thread message to the argument
|
||
|
threadMsg = (usbMesg*)arg;
|
||
|
#endif
|
||
|
|
||
|
// Thread loop
|
||
|
while (1)
|
||
|
{
|
||
|
#ifndef LIBDRAGON
|
||
|
// Wait for a USB message to arrive
|
||
|
osRecvMesg(&usbMessageQ, (OSMesg *)&threadMsg, OS_MESG_BLOCK);
|
||
|
#endif
|
||
|
|
||
|
// Ensure there's no data in the USB (which handles MSG_READ)
|
||
|
while (usb_poll() != 0)
|
||
|
{
|
||
|
int header = usb_poll();
|
||
|
debugCommand* entry;
|
||
|
|
||
|
// RDB packets should be rerouted to the RDB thread
|
||
|
#if USE_RDBTHREAD
|
||
|
if (USBHEADER_GETTYPE(header) == DATATYPE_RDBPACKET)
|
||
|
{
|
||
|
#ifndef LIBDRAGON
|
||
|
osSendMesg(&rdbMessageQ, (OSMesg)MSG_RDB_PACKET, OS_MESG_BLOCK);
|
||
|
#else
|
||
|
|
||
|
// Exceptional case, handle pausing through CTRL+C
|
||
|
char packetstart;
|
||
|
usb_read(&packetstart, 1);
|
||
|
if (packetstart == '\x03')
|
||
|
{
|
||
|
usb_rewind(1);
|
||
|
debug_thread_rdb_pause();
|
||
|
}
|
||
|
else
|
||
|
debug_thread_rdb_loop(NULL);
|
||
|
#endif
|
||
|
continue;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// Ensure we're receiving a text command
|
||
|
if (USBHEADER_GETTYPE(header) != DATATYPE_TEXT)
|
||
|
{
|
||
|
errortype = USBERROR_NOTTEXT;
|
||
|
usb_purge();
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Initialize the command trackers
|
||
|
debug_command_totaltokens = 0;
|
||
|
debug_command_current = 0;
|
||
|
|
||
|
// Break the USB command into parts
|
||
|
debug_commands_setup();
|
||
|
|
||
|
// Ensure we don't read past our buffer
|
||
|
if (debug_sizecommand() > BUFFER_SIZE)
|
||
|
{
|
||
|
errortype = USBERROR_TOOMUCH;
|
||
|
usb_purge();
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Read from the USB to retrieve the command name
|
||
|
debug_parsecommand(debug_buffer);
|
||
|
|
||
|
// Iterate through the hashtable to see if we find the command
|
||
|
entry = debug_commands_hashtable[debug_buffer[0]%HASHTABLE_SIZE];
|
||
|
while (entry != NULL)
|
||
|
{
|
||
|
// If we found the command
|
||
|
if (!strncmp(debug_buffer, entry->command, debug_command_incoming_size[0]))
|
||
|
{
|
||
|
// Execute the command function and exit the while loop
|
||
|
debug_command_error = entry->execute();
|
||
|
if (debug_command_error != NULL)
|
||
|
errortype = USBERROR_CUSTOM;
|
||
|
usb_purge();
|
||
|
break;
|
||
|
}
|
||
|
entry = entry->next;
|
||
|
}
|
||
|
|
||
|
// If no command was found
|
||
|
if (entry == NULL)
|
||
|
{
|
||
|
// Purge the USB contents and print unknown command
|
||
|
usb_purge();
|
||
|
errortype = USBERROR_UNKNOWN;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Handle 64Drive button polling
|
||
|
debug_handle_64drivebutton();
|
||
|
|
||
|
// Spit out an error if there was one during the command parsing
|
||
|
if (errortype != USBERROR_NONE)
|
||
|
{
|
||
|
switch (errortype)
|
||
|
{
|
||
|
case USBERROR_NOTTEXT:
|
||
|
usb_write(DATATYPE_TEXT, "Error: USB data was not text\n", 29+1);
|
||
|
break;
|
||
|
case USBERROR_UNKNOWN:
|
||
|
usb_write(DATATYPE_TEXT, "Error: Unknown command\n", 23+1);
|
||
|
break;
|
||
|
case USBERROR_TOOMUCH:
|
||
|
usb_write(DATATYPE_TEXT, "Error: Command too large\n", 25+1);
|
||
|
break;
|
||
|
case USBERROR_CUSTOM:
|
||
|
usb_write(DATATYPE_TEXT, debug_command_error, strlen(debug_command_error)+1);
|
||
|
usb_write(DATATYPE_TEXT, "\n", 1+1);
|
||
|
break;
|
||
|
}
|
||
|
errortype = USBERROR_NONE;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Handle the other USB messages
|
||
|
if (threadMsg != NULL)
|
||
|
{
|
||
|
switch (threadMsg->msgtype)
|
||
|
{
|
||
|
case MSG_WRITE:
|
||
|
if (usb_timedout())
|
||
|
usb_sendheartbeat();
|
||
|
usb_write(threadMsg->datatype, threadMsg->buff, threadMsg->size);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If we're in libdragon, break out of the loop as we don't need it
|
||
|
#ifdef LIBDRAGON
|
||
|
break;
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifndef LIBDRAGON
|
||
|
#if OVERWRITE_OSPRINT
|
||
|
|
||
|
/*==============================
|
||
|
debug_osSyncPrintf_implementation
|
||
|
Overwrites osSyncPrintf calls with this one
|
||
|
@param Unused
|
||
|
@param The buffer with the string
|
||
|
@param The amount of characters to write
|
||
|
@returns The end of the buffer that was written to
|
||
|
==============================*/
|
||
|
|
||
|
static void* debug_osSyncPrintf_implementation(void *unused, const char *str, size_t len)
|
||
|
{
|
||
|
void* ret;
|
||
|
usbMesg msg;
|
||
|
|
||
|
// Clear the debug buffer and copy the formatted string to it
|
||
|
memset(debug_buffer, 0, len+1);
|
||
|
ret = ((char *) memcpy(debug_buffer, str, len) + len);
|
||
|
|
||
|
// Send the printf to the usb thread
|
||
|
msg.msgtype = MSG_WRITE;
|
||
|
msg.datatype = DATATYPE_TEXT;
|
||
|
msg.buff = debug_buffer;
|
||
|
msg.size = len+1;
|
||
|
osSendMesg(&usbMessageQ, (OSMesg)&msg, OS_MESG_BLOCK);
|
||
|
|
||
|
// Return the end of the buffer
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
#ifndef LIBDRAGON
|
||
|
#if USE_FAULTTHREAD
|
||
|
|
||
|
/*==============================
|
||
|
debug_printreg
|
||
|
Prints info about a register
|
||
|
@param The value of the register
|
||
|
@param The name of the register
|
||
|
@param The registry description to use
|
||
|
==============================*/
|
||
|
|
||
|
static void debug_printreg(u32 value, char *name, regDesc *desc)
|
||
|
{
|
||
|
char first = 1;
|
||
|
debug_printf("%s\t\t0x%16x <", name, value);
|
||
|
while (desc->mask != 0)
|
||
|
{
|
||
|
if ((value & desc->mask) == desc->value)
|
||
|
{
|
||
|
(first) ? (first = 0) : ((void)debug_printf(","));
|
||
|
debug_printf("%s", desc->string);
|
||
|
}
|
||
|
desc++;
|
||
|
}
|
||
|
debug_printf(">\n");
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_thread_fault
|
||
|
Handles the fault thread
|
||
|
@param Arbitrary data that the thread can receive
|
||
|
==============================*/
|
||
|
|
||
|
static void debug_thread_fault(void *arg)
|
||
|
{
|
||
|
OSMesg msg;
|
||
|
OSThread *curr;
|
||
|
|
||
|
// Create the message queue for the fault message
|
||
|
osCreateMesgQueue(&faultMessageQ, &faultMessageBuf, 1);
|
||
|
osSetEventMesg(OS_EVENT_FAULT, &faultMessageQ, (OSMesg)MSG_FAULT);
|
||
|
|
||
|
// Thread loop
|
||
|
while (1)
|
||
|
{
|
||
|
// Wait for a fault message to arrive
|
||
|
osRecvMesg(&faultMessageQ, (OSMesg *)&msg, OS_MESG_BLOCK);
|
||
|
|
||
|
// Get the faulted thread
|
||
|
curr = (OSThread *)__osGetCurrFaultedThread();
|
||
|
if (curr != NULL)
|
||
|
{
|
||
|
__OSThreadContext* context = &curr->context;
|
||
|
|
||
|
// If the debug or rdb thread crashed, restart it
|
||
|
if (curr->id == USB_THREAD_ID)
|
||
|
{
|
||
|
osCreateThread(&usbThread, USB_THREAD_ID, debug_thread_usb, 0,
|
||
|
(usbThreadStack+USB_THREAD_STACK/sizeof(u64)),
|
||
|
USB_THREAD_PRI);
|
||
|
osStartThread(&usbThread);
|
||
|
}
|
||
|
#if USE_RDBTHREAD
|
||
|
else if (curr->id == RDB_THREAD_ID)
|
||
|
{
|
||
|
osCreateThread(&rdbThread, RDB_THREAD_ID, debug_thread_rdb, (void*)osGetThreadId(NULL),
|
||
|
(rdbThreadStack+RDB_THREAD_STACK/sizeof(u64)),
|
||
|
RDB_THREAD_PRI);
|
||
|
osStartThread(&rdbThread);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// Print the basic info
|
||
|
debug_printf("Fault in thread: %d\n\n", curr->id);
|
||
|
debug_printf("pc\t\t0x%16x\n", context->pc);
|
||
|
if (assert_file == NULL)
|
||
|
debug_printreg(context->cause, "cause", causeDesc);
|
||
|
else
|
||
|
debug_printf("cause\t\tAssertion failed in file '%s', line %d.\n", assert_file, assert_line);
|
||
|
debug_printreg(context->sr, "sr", srDesc);
|
||
|
debug_printf("badvaddr\t0x%16x\n\n", context->badvaddr);
|
||
|
|
||
|
// Print the registers
|
||
|
debug_printf("at 0x%016llx v0 0x%016llx v1 0x%016llx\n", context->at, context->v0, context->v1);
|
||
|
debug_printf("a0 0x%016llx a1 0x%016llx a2 0x%016llx\n", context->a0, context->a1, context->a2);
|
||
|
debug_printf("a3 0x%016llx t0 0x%016llx t1 0x%016llx\n", context->a3, context->t0, context->t1);
|
||
|
debug_printf("t2 0x%016llx t3 0x%016llx t4 0x%016llx\n", context->t2, context->t3, context->t4);
|
||
|
debug_printf("t5 0x%016llx t6 0x%016llx t7 0x%016llx\n", context->t5, context->t6, context->t7);
|
||
|
debug_printf("s0 0x%016llx s1 0x%016llx s2 0x%016llx\n", context->s0, context->s1, context->s2);
|
||
|
debug_printf("s3 0x%016llx s4 0x%016llx s5 0x%016llx\n", context->s3, context->s4, context->s5);
|
||
|
debug_printf("s6 0x%016llx s7 0x%016llx t8 0x%016llx\n", context->s6, context->s7, context->t8);
|
||
|
debug_printf("t9 0x%016llx gp 0x%016llx sp 0x%016llx\n", context->t9, context->gp, context->sp);
|
||
|
debug_printf("s8 0x%016llx ra 0x%016llx\n\n", context->s8, context->ra);
|
||
|
|
||
|
// Print the floating point registers
|
||
|
debug_printreg(context->fpcsr, "fpcsr", fpcsrDesc);
|
||
|
debug_printf("\n");
|
||
|
debug_printf("d0 %.15e\td2 %.15e\n", context->fp0.d, context->fp2.d);
|
||
|
debug_printf("d4 %.15e\td6 %.15e\n", context->fp4.d, context->fp6.d);
|
||
|
debug_printf("d8 %.15e\td10 %.15e\n", context->fp8.d, context->fp10.d);
|
||
|
debug_printf("d12 %.15e\td14 %.15e\n", context->fp12.d, context->fp14.d);
|
||
|
debug_printf("d16 %.15e\td18 %.15e\n", context->fp16.d, context->fp18.d);
|
||
|
debug_printf("d20 %.15e\td22 %.15e\n", context->fp20.d, context->fp22.d);
|
||
|
debug_printf("d24 %.15e\td26 %.15e\n", context->fp24.d, context->fp26.d);
|
||
|
debug_printf("d28 %.15e\td30 %.15e\n", context->fp28.d, context->fp30.d);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
#if USE_RDBTHREAD
|
||
|
#ifndef LIBDRAGON
|
||
|
/*==============================
|
||
|
debug_thread_rdb
|
||
|
Handles the remote debugger thread (Libultra)
|
||
|
@param Arbitrary data that the thread can receive.
|
||
|
Used for passing the main thread ID.
|
||
|
==============================*/
|
||
|
|
||
|
static void debug_thread_rdb(void *arg)
|
||
|
{
|
||
|
OSId mainid = (OSId)arg;
|
||
|
OSThread* mainthread = &rdbThread;
|
||
|
|
||
|
// Find the main thread pointer given its ID
|
||
|
while (mainthread->id != mainid)
|
||
|
mainthread = mainthread->tlnext;
|
||
|
|
||
|
// Create the message queue for the rdb messages
|
||
|
osCreateMesgQueue(&rdbMessageQ, &rdbMessageBuf, 1);
|
||
|
|
||
|
// Thread loop
|
||
|
while (1)
|
||
|
{
|
||
|
OSMesg msg;
|
||
|
OSThread* affected = NULL;
|
||
|
|
||
|
// Wait for an rdb message to arrive
|
||
|
osRecvMesg(&rdbMessageQ, &msg, OS_MESG_BLOCK);
|
||
|
|
||
|
// Exceptional case, handle pausing through CTRL+C
|
||
|
if (USBHEADER_GETTYPE(usb_poll()) == DATATYPE_RDBPACKET)
|
||
|
{
|
||
|
char packetstart;
|
||
|
usb_read(&packetstart, 1);
|
||
|
if (packetstart == '\x03')
|
||
|
msg = (OSMesg)MSG_RDB_PAUSE;
|
||
|
usb_rewind(1);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Check what message we received
|
||
|
switch ((s32)msg)
|
||
|
{
|
||
|
case MSG_RDB_PACKET:
|
||
|
break; // Do nothing
|
||
|
case MSG_RDB_BPHIT:
|
||
|
affected = mainthread;
|
||
|
debug_rdbpaused = TRUE;
|
||
|
debug_pausetime = osGetTime();
|
||
|
|
||
|
// Find out which thread hit the bp exception
|
||
|
while (1)
|
||
|
{
|
||
|
if (affected->flags & OS_FLAG_CPU_BREAK)
|
||
|
break;
|
||
|
affected = affected->tlnext;
|
||
|
}
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_RDBPACKET, "T05swbreak:;", 12+1);
|
||
|
break;
|
||
|
case MSG_RDB_PAUSE:
|
||
|
affected = mainthread;
|
||
|
debug_rdbpaused = TRUE;
|
||
|
debug_pausetime = osGetTime();
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Do the RDB thread main loop
|
||
|
debug_thread_rdb_loop(affected);
|
||
|
}
|
||
|
}
|
||
|
#else
|
||
|
|
||
|
/*==============================
|
||
|
debug_thread_rdb_pause
|
||
|
"Pauses" the program execution in Libdragon.
|
||
|
@param The received exception
|
||
|
==============================*/
|
||
|
|
||
|
static void debug_thread_rdb_pause()
|
||
|
{
|
||
|
debug_ismanualpause = TRUE;
|
||
|
debug_pausetime = C0_COUNT();
|
||
|
asm volatile("break"); // Jank workaround because I can't "message" the exception handler "thread"
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_thread_rdb
|
||
|
Handles the remote debugger logic (Libdragon)
|
||
|
@param The received exception
|
||
|
==============================*/
|
||
|
|
||
|
static void debug_thread_rdb(exception_t* exc)
|
||
|
{
|
||
|
switch (exc->code)
|
||
|
{
|
||
|
case EXCEPTION_CODE_BREAKPOINT:
|
||
|
debug_rdbpaused = TRUE;
|
||
|
if (!debug_ismanualpause)
|
||
|
{
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_RDBPACKET, "T05swbreak:;", 12+1);
|
||
|
}
|
||
|
debug_pausetime = C0_COUNT();
|
||
|
debug_thread_rdb_loop(exc);
|
||
|
if (debug_ismanualpause)
|
||
|
{
|
||
|
debug_ismanualpause = FALSE;
|
||
|
exc->regs->epc += 4; // Gotta increment PC otherwise the program will likely hit the breakpoint again in debug_initialize
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
exception_default_handler(exc);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_thread_rdb_loop
|
||
|
Handles the loop of the remote debugger thread
|
||
|
@param (Libultra) A pointer to the affected thread
|
||
|
(Libdragon) A pointer to the exception struct
|
||
|
==============================*/
|
||
|
|
||
|
static void debug_thread_rdb_loop(OSThread* affected)
|
||
|
{
|
||
|
// Handle the RDB packet
|
||
|
do
|
||
|
{
|
||
|
int usbheader = usb_poll();
|
||
|
if (USBHEADER_GETTYPE(usbheader) == DATATYPE_RDBPACKET)
|
||
|
{
|
||
|
int i;
|
||
|
u8 found = FALSE;
|
||
|
u32 size = USBHEADER_GETSIZE(usbheader);
|
||
|
|
||
|
// Read the GDB packet from USB
|
||
|
memset(debug_buffer, 0, BUFFER_SIZE);
|
||
|
usb_read(&debug_buffer, (size <= BUFFER_SIZE) ? size : BUFFER_SIZE);
|
||
|
|
||
|
// Run a function based on what we received
|
||
|
for (i=0; i<(sizeof(lut_rdbpackets)/sizeof(lut_rdbpackets[0])); i++)
|
||
|
{
|
||
|
if (!strncmp(lut_rdbpackets[i].command, debug_buffer, strlen(lut_rdbpackets[i].command)))
|
||
|
{
|
||
|
found = TRUE;
|
||
|
lut_rdbpackets[i].func(affected);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If we didn't find a supported command, then reply back with nothing
|
||
|
if (!found)
|
||
|
{
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_RDBPACKET, "\0", 1);
|
||
|
}
|
||
|
}
|
||
|
usb_purge();
|
||
|
}
|
||
|
while (debug_rdbpaused); // Loop forever while we are paused
|
||
|
}
|
||
|
|
||
|
/*==============================
|
||
|
debug_rdb_qsupported
|
||
|
Responds to GDB with the supported features
|
||
|
@param The affected thread, if any
|
||
|
==============================*/
|
||
|
|
||
|
static void debug_rdb_qsupported(OSThread* t)
|
||
|
{
|
||
|
sprintf(debug_buffer, "swbreak+");
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_RDBPACKET, debug_buffer, strlen(debug_buffer));
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_rdb_haltreason
|
||
|
Responds to GDB with the halt reason
|
||
|
@param The affected thread, if any
|
||
|
==============================*/
|
||
|
|
||
|
static void debug_rdb_haltreason(OSThread* t)
|
||
|
{
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_RDBPACKET, "S02", 3+1);
|
||
|
}
|
||
|
|
||
|
|
||
|
#ifndef LIBDRAGON
|
||
|
/*==============================
|
||
|
register_fromindex
|
||
|
Gets a register type from a given index
|
||
|
@param The affected thread context
|
||
|
@param The register index
|
||
|
@returns The regType that matches the given index
|
||
|
==============================*/
|
||
|
|
||
|
static regType register_fromindex(__OSThreadContext* context, int index)
|
||
|
{
|
||
|
regType reg = {0, NULL};
|
||
|
switch (index)
|
||
|
{
|
||
|
case 0: // Zero register
|
||
|
case 26: // K0
|
||
|
case 27: // K1
|
||
|
case 71: // FCR0
|
||
|
return reg;
|
||
|
case 32: reg.size = 4; reg.ptr = ((u32*)&context->sr); return reg;
|
||
|
case 33: reg.size = 8; reg.ptr = ((u64*)&context->lo); return reg;
|
||
|
case 34: reg.size = 8; reg.ptr = ((u64*)&context->hi); return reg;
|
||
|
case 35: reg.size = 4; reg.ptr = ((u32*)&context->badvaddr); return reg;
|
||
|
case 36: reg.size = 4; reg.ptr = ((u32*)&context->cause); return reg;
|
||
|
case 37: reg.size = 4; reg.ptr = ((u32*)&context->pc); return reg;
|
||
|
case 70: reg.size = 4; reg.ptr = ((u32*)&context->fpcsr); return reg;
|
||
|
default:
|
||
|
if (index < 32)
|
||
|
{
|
||
|
reg.size = 8;
|
||
|
if (index > 27)
|
||
|
reg.ptr = ((u64*)&context->gp)+(index-28);
|
||
|
else
|
||
|
reg.ptr = ((u64*)&context->at)+(index-1);
|
||
|
return reg;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
reg.size = 8;
|
||
|
reg.ptr = ((u64*)&context->fp0)+(((u32)(index-38))/2);
|
||
|
return reg;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#else
|
||
|
/*==============================
|
||
|
register_fromindex
|
||
|
Gets a register type from a given index
|
||
|
@param The affected thread context
|
||
|
@param The register index
|
||
|
@returns The regType that matches the given index
|
||
|
==============================*/
|
||
|
|
||
|
static regType register_fromindex(reg_block_t* context, int index)
|
||
|
{
|
||
|
regType reg = {0, NULL};
|
||
|
switch (index)
|
||
|
{
|
||
|
case 0: // Zero register
|
||
|
case 26: // K0
|
||
|
case 27: // K1
|
||
|
case 71: // FCR0
|
||
|
case 35: // BadVAddr
|
||
|
case 37: // pc
|
||
|
return reg;
|
||
|
case 32: reg.size = 4; reg.ptr = ((u32*)&context->sr); return reg;
|
||
|
case 33: reg.size = 8; reg.ptr = ((u64*)&context->lo); return reg;
|
||
|
case 34: reg.size = 8; reg.ptr = ((u64*)&context->hi); return reg;
|
||
|
case 36: reg.size = 4; reg.ptr = ((u32*)&context->cr); return reg;
|
||
|
case 70: reg.size = 4; reg.ptr = ((u32*)&context->fc31); return reg;
|
||
|
default:
|
||
|
if (index < 32)
|
||
|
{
|
||
|
reg.size = 8;
|
||
|
reg.ptr = (u64*)&context->gpr[index-1];
|
||
|
return reg;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
reg.size = 8;
|
||
|
reg.ptr = (u64*)&context->fpr[index-38];
|
||
|
return reg;
|
||
|
}
|
||
|
}
|
||
|
return reg;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_rdb_printreg_rle
|
||
|
Sprintf's a register value into a buffer,
|
||
|
compressed with Run-Length Encoding
|
||
|
@param The buffer to write to
|
||
|
@param The register type
|
||
|
@returns The number of bytes written
|
||
|
==============================*/
|
||
|
|
||
|
static u32 debug_rdb_printreg_rle(char* buf, regType reg)
|
||
|
{
|
||
|
if (reg.ptr != NULL)
|
||
|
{
|
||
|
int i;
|
||
|
u8 count;
|
||
|
u32 totalwrote = 0;
|
||
|
char last;
|
||
|
char temp[REGISTER_SIZE+1];
|
||
|
|
||
|
// Read the register value into a string
|
||
|
if (reg.size == 8)
|
||
|
sprintf(temp, "%016llx", (u64)(*(u64*)reg.ptr));
|
||
|
else
|
||
|
sprintf(temp, "%016llx", (u64)(*(u32*)reg.ptr));
|
||
|
last = temp[0];
|
||
|
count = 1;
|
||
|
|
||
|
// Find repeated characters
|
||
|
for (i=1; i<(REGISTER_SIZE+1); i++)
|
||
|
{
|
||
|
if (temp[i] != last)
|
||
|
{
|
||
|
// If the repeat was more than 3, then it's worth RLE'ing
|
||
|
if (count > 3)
|
||
|
{
|
||
|
// Because 6 (#) and 7 ($) are special characters in GDB, we have to do them differently
|
||
|
if (count == 7)
|
||
|
totalwrote += sprintf(buf+totalwrote, "%c*\"%c", last, last);
|
||
|
else if (count == 8)
|
||
|
totalwrote += sprintf(buf+totalwrote, "%c*\"%c%c", last, last, last);
|
||
|
else
|
||
|
totalwrote += sprintf(buf+totalwrote, "%c*%c", last, ' '+(count-4));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int j;
|
||
|
for (j=0; j<count; j++)
|
||
|
*(buf+totalwrote+j) = last;
|
||
|
*(buf+totalwrote+j) = '\0';
|
||
|
totalwrote += j;
|
||
|
}
|
||
|
last = temp[i];
|
||
|
count = 1;
|
||
|
}
|
||
|
else
|
||
|
count++;
|
||
|
}
|
||
|
return totalwrote;
|
||
|
}
|
||
|
return sprintf(buf, "x*,");
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_rdb_dumpregisters
|
||
|
Responds to GDB with a dump of all registers
|
||
|
@param The affected thread, if any
|
||
|
==============================*/
|
||
|
|
||
|
static void debug_rdb_dumpregisters(OSThread* t)
|
||
|
{
|
||
|
if (t != NULL)
|
||
|
{
|
||
|
int i;
|
||
|
u32 chunkcount = 2+(REGISTER_COUNT*REGISTER_SIZE)/BUFFER_SIZE;
|
||
|
u32 header[2];
|
||
|
u32 offset = 0;
|
||
|
#ifndef LIBDRAGON
|
||
|
__OSThreadContext* context = &t->context;
|
||
|
#else
|
||
|
reg_block_t* context = t->regs;
|
||
|
#endif
|
||
|
|
||
|
// Start by sending a HEADER packet with the chunk count
|
||
|
header[0] = DATATYPE_RDBPACKET;
|
||
|
header[1] = chunkcount;
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_HEADER, &header, sizeof(u32)*2);
|
||
|
|
||
|
// Perform the humpty dumpty
|
||
|
offset += sprintf(debug_buffer+offset, "0*,"); // Zero register
|
||
|
for (i=1; i<REGISTER_COUNT; i++)
|
||
|
{
|
||
|
if (i == 71)
|
||
|
offset += sprintf(debug_buffer+offset, "0*&800b11"); // FCR0 is an edge case
|
||
|
#ifdef LIBDRAGON
|
||
|
else if (i == 35)
|
||
|
offset += sprintf(debug_buffer+offset, "%016llx", (s64)C0_BADVADDR());
|
||
|
else if (i == 37)
|
||
|
offset += sprintf(debug_buffer+offset, "%016llx", (s64)((s32)context->epc));
|
||
|
#endif
|
||
|
else
|
||
|
{
|
||
|
regType reg = register_fromindex(context, i);
|
||
|
offset += debug_rdb_printreg_rle(debug_buffer+offset, reg);
|
||
|
}
|
||
|
|
||
|
// Send a chunk if we're about to overrun the debug buffer
|
||
|
if ((strlen(debug_buffer)+REGISTER_SIZE+1) > BUFFER_SIZE || i == (REGISTER_COUNT-1))
|
||
|
{
|
||
|
usb_write(DATATYPE_RDBPACKET, debug_buffer, strlen(debug_buffer)+1);
|
||
|
memset(debug_buffer, 0, BUFFER_SIZE);
|
||
|
offset = 0;
|
||
|
chunkcount--;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Finish sending the other chunks
|
||
|
for (i=chunkcount; i>=0; i--)
|
||
|
usb_write(DATATYPE_RDBPACKET, "\0", 1);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_RDBPACKET, "E00", 3+1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
hex2u64
|
||
|
Converts a string containing a hexadecimal value
|
||
|
into a number. This exists because strtol is broken
|
||
|
on ModernSDK.
|
||
|
@param The string with the hexadecimal number
|
||
|
@returns The converted value
|
||
|
==============================*/
|
||
|
|
||
|
static u64 hex2u64(char* addr)
|
||
|
{
|
||
|
int i = 0;
|
||
|
u64 ret = 0;
|
||
|
while (addr[i] != '\0')
|
||
|
{
|
||
|
u32 val;
|
||
|
if (addr[i] <= '9')
|
||
|
val = addr[i]-'0';
|
||
|
else if (addr[i] <= 'F')
|
||
|
val = 10 + addr[i] - 'A';
|
||
|
else
|
||
|
val = 10 + addr[i] - 'a';
|
||
|
ret = (ret << 4) | (val & 0xF);
|
||
|
i++;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_rdb_writeregisters
|
||
|
Writes a set of registers from a GDB packet
|
||
|
@param The affected thread, if any
|
||
|
==============================*/
|
||
|
|
||
|
static void debug_rdb_writeregisters(OSThread* t)
|
||
|
{
|
||
|
if (t != NULL)
|
||
|
{
|
||
|
int i;
|
||
|
#ifndef LIBDRAGON
|
||
|
__OSThreadContext* context = &t->context;
|
||
|
#else
|
||
|
reg_block_t* context = t->regs;
|
||
|
#endif
|
||
|
|
||
|
// The incoming data probably won't fit in the buffer, so we'll go bit by bit
|
||
|
usb_rewind(BUFFER_SIZE);
|
||
|
|
||
|
// Skip the 'G' at the start of the command
|
||
|
usb_skip(1);
|
||
|
|
||
|
// Do the writing
|
||
|
for (i=0; i<REGISTER_COUNT; i++)
|
||
|
{
|
||
|
char val[REGISTER_SIZE+1];
|
||
|
regType reg = register_fromindex(context, i);
|
||
|
|
||
|
// Stop if there's no more register values to read
|
||
|
if (USBHEADER_GETSIZE(usb_poll()) < REGISTER_SIZE)
|
||
|
break;
|
||
|
|
||
|
// Read the register and get it's value
|
||
|
usb_read(val, REGISTER_SIZE);
|
||
|
val[REGISTER_SIZE] = '\0';
|
||
|
if (val[0] != 'x' && reg.ptr != NULL)
|
||
|
{
|
||
|
if (reg.size == 4)
|
||
|
(*(vu32*)reg.ptr) = (u32)hex2u64(val);
|
||
|
else
|
||
|
(*(vu64*)reg.ptr) = (u64)hex2u64(val);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// Done
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_RDBPACKET, "OK", 2+1);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_RDBPACKET, "E00", 3+1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_rdb_translateaddr
|
||
|
Translates an address from GDB into a valid value
|
||
|
@param The address that we received
|
||
|
@returns The corrected address
|
||
|
==============================*/
|
||
|
|
||
|
static u32 debug_rdb_translateaddr(u32 addr)
|
||
|
{
|
||
|
if ((addr & 0xFF000000) != 0xA4000000 && (addr & 0xFF000000) != 0x04000000)
|
||
|
{
|
||
|
#ifndef LIBDRAGON
|
||
|
addr = (u32)osVirtualToPhysical((u32*)addr);
|
||
|
#else
|
||
|
u32 osMemSize = get_memory_size();
|
||
|
addr = (u32)PhysicalAddr((u32*)addr);
|
||
|
#endif
|
||
|
addr = (addr <= osMemSize) ? (u32)OS_PHYSICAL_TO_K0(addr) : 0;
|
||
|
}
|
||
|
else
|
||
|
addr = (u32)OS_PHYSICAL_TO_K1(addr & 0x0FFFFFFF);
|
||
|
return addr;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_rdb_readmemory
|
||
|
Responds to GDB with a memory read
|
||
|
@param The affected thread, if any
|
||
|
==============================*/
|
||
|
|
||
|
static void debug_rdb_readmemory(OSThread* t)
|
||
|
{
|
||
|
int i;
|
||
|
u32 written = 0;
|
||
|
u32 read = 0;
|
||
|
u32 addr;
|
||
|
u32 size;
|
||
|
u32 chunkcount;
|
||
|
u32 header[2];
|
||
|
u8 validaddress = FALSE;
|
||
|
char command[32];
|
||
|
char* commandp = &command[0];
|
||
|
#ifdef LIBDRAGON
|
||
|
u32 osMemSize = get_memory_size();
|
||
|
#endif
|
||
|
strcpy(commandp, debug_buffer);
|
||
|
|
||
|
// Skip the 'm' at the start of the command
|
||
|
commandp++;
|
||
|
|
||
|
// Extract the address value
|
||
|
strtok(commandp, ",");
|
||
|
addr = (u32)hex2u64(commandp);
|
||
|
|
||
|
// Extract the size value
|
||
|
commandp = strtok(NULL, ",");
|
||
|
size = (u32)hex2u64(commandp);
|
||
|
chunkcount = 2+size/128;
|
||
|
|
||
|
// Start by sending a HEADER packet with the chunk count
|
||
|
header[0] = DATATYPE_RDBPACKET;
|
||
|
header[1] = chunkcount;
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_HEADER, &header, sizeof(u32)*2);
|
||
|
|
||
|
// We need to translate the address before trying to read it
|
||
|
addr = debug_rdb_translateaddr(addr);
|
||
|
|
||
|
// Ensure we are reading a valid memory address
|
||
|
if (addr >= 0x80000000 && addr < 0x80000000 + osMemSize)
|
||
|
{
|
||
|
#ifndef LIBDRAGON
|
||
|
osWritebackDCache((u32*)addr, size);
|
||
|
#else
|
||
|
data_cache_hit_writeback((u32*)addr, size);
|
||
|
#endif
|
||
|
validaddress = TRUE;
|
||
|
}
|
||
|
|
||
|
// Read the memory address, one byte at a time
|
||
|
while (read < size)
|
||
|
{
|
||
|
u8 val = 0;
|
||
|
if (validaddress)
|
||
|
val = *((vu8*)(addr+read));
|
||
|
written += sprintf(debug_buffer+written, "%02x", val);
|
||
|
|
||
|
// Send the partial address dump if we're almost overrunning the buffer, or if we've finished
|
||
|
read++;
|
||
|
if (written+3 >= BUFFER_SIZE || read == size)
|
||
|
{
|
||
|
usb_write(DATATYPE_RDBPACKET, &debug_buffer, strlen(debug_buffer)+1);
|
||
|
written = 0;
|
||
|
chunkcount--;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Finish sending the other chunks
|
||
|
for (i=chunkcount; i>=0; i--)
|
||
|
usb_write(DATATYPE_RDBPACKET, "\0", 1);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_rdb_writememory
|
||
|
Writes the memory from a GDB packet
|
||
|
@param The affected thread, if any
|
||
|
==============================*/
|
||
|
|
||
|
static void debug_rdb_writememory(OSThread* t)
|
||
|
{
|
||
|
int i;
|
||
|
u32 addr;
|
||
|
u32 size;
|
||
|
#ifdef LIBDRAGON
|
||
|
u32 osMemSize = get_memory_size();
|
||
|
#endif
|
||
|
char* commandp = &debug_buffer[0];
|
||
|
|
||
|
// Skip the 'M' at the start of the command
|
||
|
commandp++;
|
||
|
|
||
|
// Extract the address value
|
||
|
strtok(commandp, ",");
|
||
|
addr = (u32)hex2u64(commandp);
|
||
|
|
||
|
// Extract the size value
|
||
|
commandp = strtok(NULL, ":");
|
||
|
size = (u32)hex2u64(commandp);
|
||
|
|
||
|
// Finally, point to the data we're actually gonna write
|
||
|
commandp = strtok(NULL, "\0");
|
||
|
|
||
|
// We need to translate the address before trying to read it
|
||
|
addr = debug_rdb_translateaddr(addr);
|
||
|
|
||
|
// Ensure we are writing to a valid memory address
|
||
|
if (addr >= 0x80000000 && addr < 0x80000000 + osMemSize)
|
||
|
{
|
||
|
// Read the memory address, one byte at a time
|
||
|
for (i=0; i<size; i++)
|
||
|
{
|
||
|
char byte[3];
|
||
|
sprintf(byte, "%.2s", commandp+(i*2));
|
||
|
*(((vu8*)addr)+i) = (u8)hex2u64(byte);
|
||
|
}
|
||
|
|
||
|
// Done
|
||
|
#ifndef LIBDRAGON
|
||
|
osWritebackDCache((u32*)addr, size);
|
||
|
#else
|
||
|
data_cache_hit_writeback((u32*)addr, size);
|
||
|
#endif
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_RDBPACKET, "OK", 2+1);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_RDBPACKET, "E00", 3+1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_rdb_addbreakpoint
|
||
|
Enables a breakpoint
|
||
|
@param The affected thread, if any
|
||
|
==============================*/
|
||
|
|
||
|
void debug_rdb_addbreakpoint(OSThread* t)
|
||
|
{
|
||
|
int i;
|
||
|
u32 addr;
|
||
|
char command[32];
|
||
|
char* token = &command[0];
|
||
|
#ifdef LIBDRAGON
|
||
|
u32 osMemSize = get_memory_size();
|
||
|
#endif
|
||
|
strcpy(command, debug_buffer);
|
||
|
|
||
|
// Skip the Z0 at the start
|
||
|
token = strtok(command, ",");
|
||
|
|
||
|
// Extract the address value
|
||
|
token = strtok(NULL, ",");
|
||
|
addr = (u32)hex2u64(token);
|
||
|
|
||
|
// There's still one more byte left (the breakpoint kind) which we can ignore
|
||
|
|
||
|
// We need to translate the address before trying to read it
|
||
|
addr = debug_rdb_translateaddr(addr);
|
||
|
|
||
|
// Find an empty slot in our breakpoint array and store the breakpoint info there
|
||
|
if (addr >= 0x80000000 && addr < 0x80000000 + osMemSize)
|
||
|
{
|
||
|
for (i=0; i<BPOINT_COUNT; i++)
|
||
|
{
|
||
|
if (debug_bpoints[i].addr == (u32*)addr) // No need to re-add the bp if it already exists
|
||
|
{
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_RDBPACKET, "OK", 2+1);
|
||
|
return;
|
||
|
}
|
||
|
if (debug_bpoints[i].addr == NULL)
|
||
|
{
|
||
|
// Store the address and the instruction (the value in its memory) before we overwrite it with a breakpoint
|
||
|
debug_bpoints[i].addr = (u32*)addr;
|
||
|
debug_bpoints[i].instruction = *((u32*)addr);
|
||
|
|
||
|
// A breakpoint on the R4300 is any invalid instruction (It's an exception).
|
||
|
// The first 6 bits of the opcodes are reserved for the instruction itself.
|
||
|
// So since we have a range of values, we can encode the index into the instruction itself, in the middle 20 bits
|
||
|
*((vu32*)addr) = MAKE_BREAKPOINT_INDEX(i+1);
|
||
|
#ifndef LIBDRAGON
|
||
|
osWritebackDCache((u32*)addr, 4);
|
||
|
osInvalICache((u32*)addr, 4);
|
||
|
#else
|
||
|
data_cache_hit_writeback((u32*)addr, 4);
|
||
|
inst_cache_hit_invalidate((u32*)addr, 4);
|
||
|
#endif
|
||
|
|
||
|
// Tell GDB we succeeded
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_RDBPACKET, "OK", 2+1);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Some failure happend
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_RDBPACKET, "E00", 3+1);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_rdb_removebreakpoint
|
||
|
Disables a breakpoint
|
||
|
@param The affected thread, if any
|
||
|
==============================*/
|
||
|
|
||
|
static void debug_rdb_removebreakpoint(OSThread* t)
|
||
|
{
|
||
|
int index;
|
||
|
u32 addr;
|
||
|
char command[32];
|
||
|
char* commandp = &command[0];
|
||
|
#ifdef LIBDRAGON
|
||
|
u32 osMemSize = get_memory_size();
|
||
|
#endif
|
||
|
strcpy(commandp, debug_buffer);
|
||
|
|
||
|
// Skip the Z0 at the start
|
||
|
strtok(commandp, ",");
|
||
|
|
||
|
// Extract the address value
|
||
|
commandp = strtok(NULL, ",");
|
||
|
addr = (u32)hex2u64(commandp);
|
||
|
|
||
|
// There's still one more byte left (the breakpoint kind) which we can ignore
|
||
|
|
||
|
// We need to translate the address before trying to read it
|
||
|
addr = debug_rdb_translateaddr(addr);
|
||
|
|
||
|
// Ensure the address has a valid breakpoint
|
||
|
if (addr >= 0x80000000 && addr < 0x80000000 + osMemSize)
|
||
|
{
|
||
|
index = GET_BREAKPOINT_INDEX(*((u32*)addr))-1;
|
||
|
if (debug_bpoints[index].addr == (u32*)addr)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
// Remove the breakpoint
|
||
|
*((vu32*)addr) = debug_bpoints[index].instruction;
|
||
|
#ifndef LIBDRAGON
|
||
|
osWritebackDCache((u32*)addr, 4);
|
||
|
osInvalICache((u32*)addr, 4);
|
||
|
#else
|
||
|
data_cache_hit_writeback((u32*)addr, 4);
|
||
|
inst_cache_hit_invalidate((u32*)addr, 4);
|
||
|
#endif
|
||
|
|
||
|
// Move all the breakpoints in front of it back
|
||
|
for (i=index; i<BPOINT_COUNT; i++)
|
||
|
{
|
||
|
if (debug_bpoints[i].addr == NULL)
|
||
|
break;
|
||
|
if (i == BPOINT_COUNT-1)
|
||
|
{
|
||
|
debug_bpoints[i].addr = NULL;
|
||
|
debug_bpoints[i].instruction = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
debug_bpoints[i].addr = debug_bpoints[i+1].addr;
|
||
|
debug_bpoints[i].instruction = debug_bpoints[i+1].instruction;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Tell GDB we succeeded
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_RDBPACKET, "OK", 2+1);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Some failure happend
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_RDBPACKET, "E00", 3+1);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_rdb_continue
|
||
|
Handles continue
|
||
|
@param The affected thread, if any
|
||
|
==============================*/
|
||
|
|
||
|
static void debug_rdb_continue(OSThread* t)
|
||
|
{
|
||
|
debug_rdbpaused = FALSE;
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_RDBPACKET, "OK", 2+1);
|
||
|
#ifndef LIBDRAGON
|
||
|
osSetTime(debug_pausetime);
|
||
|
#else
|
||
|
C0_WRITE_COUNT(debug_pausetime);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
/*==============================
|
||
|
debug_rdb_pause
|
||
|
Handles pausing from CTRL+C
|
||
|
@param The affected thread, if any
|
||
|
==============================*/
|
||
|
|
||
|
static void debug_rdb_pause(OSThread* t)
|
||
|
{
|
||
|
debug_rdbpaused = TRUE;
|
||
|
usb_purge();
|
||
|
usb_write(DATATYPE_RDBPACKET, "S02", 3+1);
|
||
|
#ifndef LIBDRAGON
|
||
|
debug_pausetime = osGetTime();
|
||
|
#else
|
||
|
debug_pausetime = C0_COUNT();
|
||
|
#endif
|
||
|
}
|
||
|
#endif
|
||
|
#endif
|