Last active
July 30, 2025 06:05
-
-
Save impiaaa/a523d49fe04c9e5c0c610ff2cfdc139e to your computer and use it in GitHub Desktop.
GDB stub for PlayStation 1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /**************************************************************************** | |
| * | |
| * psx-stub.c | |
| * low level support for gdb debugger on PS1. | |
| * | |
| * Contributors: Glenn Engel, Lake Stevens Instrument Division | |
| * Stu Grossman, Cygnus Support | |
| * Spencer Alves | |
| * | |
| * To enable debugger support, two things need to happen. One, a | |
| * call to set_debug_traps() is necessary in order to allow any breakpoints | |
| * or error conditions to be properly intercepted and reported to gdb. | |
| * Two, a breakpoint needs to be generated to begin communication. This | |
| * is most easily accomplished with asm("break") | |
| * | |
| ************* | |
| * | |
| * The following gdb commands are supported: | |
| * | |
| * command function Return value | |
| * | |
| * g return the value of the CPU registers hex data or ENN | |
| * G set the value of the CPU registers OK or ENN | |
| * | |
| * mAA..AA,LLLL Read LLLL bytes at address AA..AA hex data or ENN | |
| * MAA..AA,LLLL: Write LLLL bytes at address AA.AA OK or ENN | |
| * | |
| * c Resume at current address SNN ( signal NN) | |
| * cAA..AA Continue at address AA..AA SNN | |
| * | |
| * ? What was the last sigval ? SNN (signal NN) | |
| * sAA..AA Single step at address AA..AA | |
| * | |
| * All commands and responses are sent with a packet which includes a | |
| * checksum. A packet consists of | |
| * | |
| * $<packet info>#<checksum>. | |
| * | |
| * where | |
| * <packet info> :: <characters representing the command or response> | |
| * <checksum> :: < two hex digits computed as modulo 256 sum of | |
| *<packetinfo>> | |
| * | |
| * When a packet is received, it is first acknowledged with either '+' or '-'. | |
| * '+' indicates a successful transfer. '-' indicates a failed transfer. | |
| * | |
| * Example: | |
| * | |
| * Host: Reply: | |
| * $m0,10#2a +$00010203040506070809101112131415#42 | |
| * | |
| ****************************************************************************/ | |
| #include <errno.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <fs.h> | |
| #include <ioctl.h> | |
| #include <kernel.h> | |
| // try to detect PSYQ usage | |
| #ifdef LMAX | |
| #define snprintf(str, size, format, ...) \ | |
| sprintf (str, format, __VA_OPT__ (, ) __VA_ARGS__) | |
| #endif | |
| /* hardware registers */ | |
| static volatile unsigned char *const SIO1_DATA = (unsigned char *)0xBF801050; | |
| static volatile unsigned short *const SIO1_STAT = (unsigned short *)0xBF801054; | |
| static volatile unsigned short *const SIO1_MODE = (unsigned short *)0xBF801058; | |
| static volatile unsigned short *const SIO1_CTRL = (unsigned short *)0xBF80105A; | |
| static volatile unsigned short *const SIO1_BAUD = (unsigned short *)0xBF80105E; | |
| static volatile unsigned long *const I_STAT = (unsigned long *)0xBF801070; | |
| static volatile unsigned long *const I_MASK = (unsigned long *)0xBF801074; | |
| /* system defines (copied from PSYQ; different from POSIX) */ | |
| #define FREAD 0x0001 /* readable */ | |
| #define FWRITE 0x0002 /* writable */ | |
| #define FNBLOCK 0x0004 /* non-blocking reads */ | |
| #define FRLOCK 0x0010 /* read locked (non-shared) */ | |
| #define FWLOCK 0x0020 /* write locked (non-shared) */ | |
| #define FAPPEND 0x0100 /* append on each write */ | |
| #define FCREAT 0x0200 /* create if nonexistant */ | |
| #define FTRUNC 0x0400 /* truncate to zero length */ | |
| #define FSCAN 0x1000 /* scan type */ | |
| #define FRCOM 0x2000 /* remote command entry */ | |
| #define FNBUF 0x4000 /* no ring buf. and console interrupt */ | |
| #define FASYNC 0x8000 /* asyncronous i/o */ | |
| /* register access */ | |
| #define REG_C0R14_EPC 32 | |
| #define REG_MDHI 33 | |
| #define REG_MDLO 34 | |
| #define REG_C0R12_SR 35 | |
| #define REG_C0R13_CAUSE 36 | |
| register unsigned int cp0bpc asm ("c0r3"); | |
| register unsigned int cp0bda asm ("c0r5"); | |
| register unsigned int cp0tar asm ("c0r6"); | |
| register unsigned int cp0dcic asm ("c0r7"); | |
| register unsigned int cp0bpcm asm ("c0r11"); | |
| /* signals for stop response (normally in signals.h, but psyq doesn't have | |
| * that) */ | |
| #define SIGINT 2 /* interrupt */ | |
| #define SIGHUP 1 /* hangup */ | |
| #define SIGILL 4 /* illegal instruction (not reset when caught) */ | |
| #define SIGTRAP 5 /* trace trap (not reset when caught) */ | |
| #define SIGFPE 8 /* floating point exception */ | |
| #define SIGBUS 10 /* bus error */ | |
| #define SIGSEGV 11 /* segmentation violation */ | |
| #define SIGSYS 12 /* bad argument to system call */ | |
| /************************************************************************ | |
| * system calls | |
| */ | |
| static inline int | |
| EnterCriticalSection () | |
| { | |
| register volatile int n asm ("a0") = 1; | |
| register volatile int r asm ("v0"); | |
| __asm__ volatile ("syscall\n" | |
| : "=r"(n), "=r"(r) | |
| : "r"(n) | |
| : "at", "v1", "a1", "a2", "a3", "t0", "t1", "t2", "t3", | |
| "t4", "t5", "t6", "t7", "t8", "t9", "memory"); | |
| return r; | |
| } | |
| static inline void | |
| ExitCriticalSection () | |
| { | |
| register volatile int n asm ("a0") = 2; | |
| __asm__ volatile ("syscall\n" | |
| : "=r"(n) | |
| : "r"(n) | |
| : "at", "v1", "a1", "a2", "a3", "t0", "t1", "t2", "t3", | |
| "t4", "t5", "t6", "t7", "t8", "t9", "memory"); | |
| } | |
| static inline void | |
| FlushCache (void) | |
| { | |
| register volatile int n asm ("t1") = 0x44; | |
| __asm__ volatile ("" : "=r"(n) : "r"(n)); | |
| ((void (*) (void))0xA0) (); | |
| } | |
| static inline void | |
| _boot (void) | |
| { | |
| register volatile int n asm ("t1") = 0xA0; | |
| __asm__ volatile ("" : "=r"(n) : "r"(n)); | |
| ((void (*) (void))0xA0) (); | |
| } | |
| static inline long | |
| OpenEvent (unsigned long desc, long spec, long mode, long (*func) (void)) | |
| { | |
| register volatile int n asm ("t1") = 0x08; | |
| __asm__ volatile ("" : "=r"(n) : "r"(n)); | |
| return ((long (*) (unsigned long, long, long, long (*) (void)))0xB0) ( | |
| desc, spec, mode, func); | |
| } | |
| static inline long | |
| CloseEvent (long event) | |
| { | |
| register volatile int n asm ("t1") = 0x09; | |
| __asm__ volatile ("" : "=r"(n) : "r"(n)); | |
| return ((long (*) (long))0xB0) (event); | |
| } | |
| static inline long | |
| EnableEvent (long event) | |
| { | |
| register volatile int n asm ("t1") = 0x0C; | |
| __asm__ volatile ("" : "=r"(n) : "r"(n)); | |
| return ((long (*) (long))0xB0) (event); | |
| } | |
| static inline long | |
| DisableEvent (long event) | |
| { | |
| register volatile int n asm ("t1") = 0x0D; | |
| __asm__ volatile ("" : "=r"(n) : "r"(n)); | |
| return ((long (*) (long))0xB0) (event); | |
| } | |
| static inline void | |
| ReturnFromException (void) | |
| { | |
| register volatile int n asm ("t1") = 0x17; | |
| __asm__ volatile ("" : "=r"(n) : "r"(n)); | |
| ((void (*) (void))0xB0) (); | |
| } | |
| static inline long | |
| AddDrv (const struct device_table *device_info) | |
| { | |
| register volatile int n asm ("t1") = 0x47; | |
| __asm__ volatile ("" : "=r"(n) : "r"(n)); | |
| return ((long (*) (const struct device_table *))0xB0) (device_info); | |
| } | |
| static inline void | |
| DelDrv (const char *device_name) | |
| { | |
| register volatile int n asm ("t1") = 0x48; | |
| __asm__ volatile ("" : "=r"(n) : "r"(n)); | |
| ((void (*) (const char *))0xB0) (device_name); | |
| } | |
| static inline void (**GetC0Table (void)) (void) | |
| { | |
| register volatile int n asm ("t1") = 0x56; | |
| __asm__ volatile ("" : "=r"(n) : "r"(n)); | |
| return (void (**) ()) ((void *(*)(void))0xB0) (); | |
| } | |
| static inline void | |
| _ioabort (const char *txt1, const char *txt2) | |
| { | |
| register volatile int n asm ("t1") = 0x19; | |
| __asm__ volatile ("" : "=r"(n) : "r"(n)); | |
| ((void (*) (const char *, const char *))0xC0) (txt1, txt2); | |
| } | |
| static inline void | |
| KernelRedirect (int ttyflag) | |
| { | |
| register volatile int n asm ("t1") = 0x1B; | |
| __asm__ volatile ("" : "=r"(n) : "r"(n)); | |
| ((void (*) (int))0xC0) (ttyflag); | |
| } | |
| static struct TCBH **ThreadHeader = (struct TCBH **)0x108; | |
| static void (**const A0Table) (void) = (void (**) (void))0x200; | |
| /************************************************************************ | |
| * low-level support routines | |
| */ | |
| /* write a single character */ | |
| static void | |
| putDebugChar (int ch) | |
| { | |
| while (((*SIO1_STAT) & 1) == 0) | |
| ; | |
| *SIO1_DATA = ch; | |
| } | |
| /* read and return a single char */ | |
| static int | |
| getDebugChar () | |
| { | |
| while (((*SIO1_STAT) & 2) == 0) | |
| ; | |
| int ch = *SIO1_DATA; | |
| return ch; | |
| } | |
| /************************************************************************/ | |
| /* BUFMAX defines the maximum number of characters in inbound/outbound | |
| * buffers*/ | |
| /* at least nRegs*4*2 are needed for register packets */ | |
| #define BUFMAX 568 | |
| static int initialized = 0; /* !0 means we've been initialized */ | |
| static void set_mem_fault_trap (int enable); | |
| static long handle_exception (void); | |
| static void handle_packet (char *ptr, int sigval); | |
| static int hexToInt (char **ptr, int *intValue); | |
| static const char hexchars[] = "0123456789abcdef"; | |
| /* Convert ch from a hex digit to an int */ | |
| static int | |
| hex (char ch) | |
| { | |
| if (ch >= 'a' && ch <= 'f') | |
| return ch - 'a' + 10; | |
| if (ch >= '0' && ch <= '9') | |
| return ch - '0'; | |
| if (ch >= 'A' && ch <= 'F') | |
| return ch - 'A' + 10; | |
| return -1; | |
| } | |
| static char remcomInBuffer[BUFMAX]; | |
| static char remcomOutBuffer[BUFMAX]; | |
| /* scan for the sequence $<data>#<checksum> */ | |
| static char * | |
| getpacket (void) | |
| { | |
| char *buffer = &remcomInBuffer[0]; | |
| unsigned char checksum; | |
| unsigned char xmitcsum; | |
| int count; | |
| unsigned char ch; | |
| *SIO1_CTRL |= 0x0020; // enable RTS | |
| while (1) | |
| { | |
| /* wait around for the start character, ignore all other characters */ | |
| do | |
| { | |
| ch = getDebugChar (); | |
| } | |
| while (ch != '$' && ch != 0x03); | |
| /* ctrl-C character - should only happen while running, caught here | |
| anyway to resolve any inconsistent state */ | |
| if (ch == 0x03) | |
| { | |
| buffer[0] = '?'; | |
| buffer[1] = '\0'; | |
| *SIO1_CTRL &= ~0x0020; // disable RTS | |
| return &buffer[0]; | |
| } | |
| retry: | |
| checksum = 0; | |
| xmitcsum = -1; | |
| count = 0; | |
| /* now, read until a # or end of buffer is found */ | |
| while (count < BUFMAX - 1) | |
| { | |
| ch = getDebugChar (); | |
| if (ch == '$') | |
| goto retry; | |
| if (ch == '#') | |
| break; | |
| checksum = checksum + ch; | |
| buffer[count] = ch; | |
| count = count + 1; | |
| } | |
| buffer[count] = 0; | |
| if (ch == '#') | |
| { | |
| xmitcsum = hex (getDebugChar ()) << 4; | |
| xmitcsum |= hex (getDebugChar ()); | |
| if (checksum != xmitcsum) | |
| { | |
| putDebugChar ('-'); /* failed checksum */ | |
| } | |
| else | |
| { | |
| putDebugChar ('+'); /* successful transfer */ | |
| /* if a sequence char is present, reply the sequence ID */ | |
| if (buffer[2] == ':') | |
| { | |
| putDebugChar (buffer[0]); | |
| putDebugChar (buffer[1]); | |
| return &buffer[3]; | |
| } | |
| *SIO1_CTRL &= ~0x0020; // disable RTS | |
| return &buffer[0]; | |
| } | |
| } | |
| } | |
| } | |
| /* send the packet in buffer. */ | |
| static void | |
| putpacket (char *buffer) | |
| { | |
| unsigned char checksum; | |
| int count; | |
| char ch; | |
| *SIO1_CTRL |= 0x0020; // enable RTS | |
| /* $<packet info>#<checksum>. */ | |
| do | |
| { | |
| putDebugChar ('$'); | |
| checksum = 0; | |
| count = 0; | |
| while ((ch = buffer[count])) | |
| { | |
| putDebugChar (ch); | |
| checksum += ch; | |
| count += 1; | |
| } | |
| putDebugChar ('#'); | |
| putDebugChar (hexchars[checksum >> 4]); | |
| putDebugChar (hexchars[checksum & 0xf]); | |
| } | |
| while (getDebugChar () != '+'); | |
| *SIO1_CTRL &= ~0x0020; // disable RTS | |
| } | |
| /* Indicate to caller of mem2hex or hex2mem that there has been an | |
| error. */ | |
| extern volatile int mem_err; | |
| /* Convert the memory pointed to by mem into hex, placing result in buf. | |
| * Return a pointer to the last char put in buf (null), in case of mem fault, | |
| * return 0. | |
| * If MAY_FAULT is non-zero, then we will handle memory faults by returning | |
| * a 0, else treat a fault like any other fault in the stub. | |
| */ | |
| static char * | |
| mem2hex (unsigned char *mem, char *buf, int count, int may_fault) | |
| { | |
| unsigned char ch; | |
| set_mem_fault_trap (may_fault); | |
| while (count-- > 0) | |
| { | |
| ch = *mem++; | |
| if (mem_err) | |
| return 0; | |
| *buf++ = hexchars[ch >> 4]; | |
| *buf++ = hexchars[ch & 0xf]; | |
| } | |
| *buf = 0; | |
| set_mem_fault_trap (0); | |
| return buf; | |
| } | |
| /* convert the hex array pointed to by buf into binary to be placed in mem | |
| * return a pointer to the character AFTER the last byte written */ | |
| static unsigned char * | |
| hex2mem (char *buf, unsigned char *mem, int count, int may_fault) | |
| { | |
| int i; | |
| unsigned char ch; | |
| set_mem_fault_trap (may_fault); | |
| for (i = 0; i < count; i++) | |
| { | |
| ch = hex (*buf++) << 4; | |
| ch |= hex (*buf++); | |
| *mem++ = ch; | |
| if (mem_err) | |
| return 0; | |
| } | |
| set_mem_fault_trap (0); | |
| return mem; | |
| } | |
| static long | |
| sioHandler (void) | |
| { | |
| const unsigned char data = *SIO1_DATA; | |
| *I_STAT &= ~0x100; // acknowledge | |
| *SIO1_CTRL |= 0x0010; // acknowledge | |
| if (data == 0x03) // ctrl-C | |
| { | |
| handle_exception (); | |
| } | |
| return 0; | |
| } | |
| /************************************************************************ | |
| * TTY support | |
| */ | |
| #if 1 | |
| static int | |
| gdb_tty_init (void) | |
| { | |
| return 0; | |
| } | |
| static int | |
| gdb_tty_open (struct iob *file, const char *path, unsigned long mode) | |
| { | |
| // we don't support reading, but we have to successfully open stdin anyway, | |
| // otherwise FlushStdInOutPut won't attempt to open stdout | |
| if (file->i_unit < 2) | |
| { | |
| return 0; | |
| } | |
| else | |
| { | |
| file->i_errno = ENXIO; | |
| return -1; | |
| } | |
| } | |
| static int | |
| do_nothing (void) | |
| { | |
| return 0; | |
| } | |
| #define STDOUTBUFSIZE 256 | |
| static unsigned char stdout_buf[STDOUTBUFSIZE]; | |
| static size_t stdout_idx = 0; | |
| static void | |
| gdb_tty_flush (void) | |
| { | |
| remcomOutBuffer[0] = 'O'; | |
| mem2hex (stdout_buf, &remcomOutBuffer[1], stdout_idx, 0); | |
| int crt = EnterCriticalSection (); | |
| putpacket (remcomOutBuffer); | |
| if (crt) | |
| ExitCriticalSection (); | |
| stdout_idx = 0; | |
| } | |
| static int | |
| gdb_tty_communicate (struct iob *file, unsigned int command) | |
| { | |
| if ((command == 2) && ((file->i_flgs & FWRITE) != 0)) | |
| { | |
| size_t left | |
| = stdout_idx >= STDOUTBUFSIZE ? 0 : STDOUTBUFSIZE - stdout_idx; | |
| size_t count = file->i_cc > left ? left : file->i_cc; | |
| memcpy (&stdout_buf[stdout_idx], file->i_ma, count); | |
| stdout_idx += count; | |
| // wait until the buffer is full or a linefeed | |
| if ((stdout_idx >= STDOUTBUFSIZE) | |
| || (memchr (file->i_ma, '\r', count) != NULL) | |
| || (memchr (file->i_ma, '\n', count) != NULL)) | |
| gdb_tty_flush (); | |
| file->i_ma += count; | |
| file->i_cc -= count; | |
| return count; | |
| } | |
| else | |
| { | |
| _ioabort ("GDB TTY: bad function", ""); | |
| return 0; | |
| } | |
| } | |
| static int | |
| gdb_tty_ioctl (struct iob *file, int command, int arg) | |
| { | |
| switch (command) | |
| { | |
| // TODO | |
| /*case FIOCSCAN: | |
| case TIOCRAW:*/ | |
| case TIOCFLUSH: | |
| gdb_tty_flush (); | |
| return 0; | |
| case TIOCREOPEN: | |
| return gdb_tty_open (file, "", arg); | |
| /*case TIOCBAUD: | |
| case TIOCEXIT: | |
| case TIOCDTR: | |
| case TIOCRTS: | |
| case TIOCLEN: | |
| case TIOCPARITY: | |
| case TIOSTATUS: | |
| case TIOERRRST: | |
| case TIOEXIST: | |
| case TIORLEN:*/ | |
| default: | |
| file->i_errno = EINVAL; | |
| return -1; | |
| } | |
| } | |
| static const struct device_table gdb_tty_drv = { | |
| .dt_string = "tty", | |
| .dt_type = DTTYPE_CHAR | DTTYPE_CONS, | |
| .dt_bsize = 1, | |
| .dt_desc = "GDB", | |
| .dt_init = gdb_tty_init, | |
| .dt_open = (int (*) ())gdb_tty_open, | |
| .dt_strategy = (int (*) ())gdb_tty_communicate, | |
| .dt_close = do_nothing, | |
| .dt_ioctl = (int (*) ())gdb_tty_ioctl, | |
| .dt_read = do_nothing, | |
| .dt_write = do_nothing, | |
| .dt_delete = do_nothing, | |
| .dt_undelete = do_nothing, | |
| .dt_firstfile = do_nothing, | |
| .dt_nextfile = do_nothing, | |
| .dt_format = do_nothing, | |
| .dt_cd = do_nothing, | |
| .dt_rename = do_nothing, | |
| .dt_remove = do_nothing, | |
| .dt_else = do_nothing, | |
| }; | |
| static void | |
| AddTtyDrv (void) | |
| { | |
| AddDrv (&gdb_tty_drv); | |
| } | |
| #endif | |
| /************************************************************************ | |
| * "File-I/O" host filesystem support | |
| */ | |
| #if 1 | |
| static int | |
| gdb_fs_syscall (struct iob *file) | |
| { | |
| while (1) | |
| { | |
| char *ptr = getpacket (); | |
| if (*ptr == 'F') | |
| { | |
| ptr++; | |
| int ret; | |
| hexToInt (&ptr, &ret); | |
| if (*ptr == '\0') | |
| return ret; | |
| else if (*ptr != ',') | |
| { | |
| snprintf (remcomOutBuffer, BUFMAX, | |
| "E.%s:%d: got unexpected character '%c' (0x%x)", | |
| __FILE__, __LINE__, *ptr, *ptr); | |
| putpacket (remcomOutBuffer); | |
| continue; | |
| } | |
| ptr++; | |
| hexToInt (&ptr, file == NULL ? &errno : &file->i_errno); | |
| if (*ptr == '\0') | |
| return ret; | |
| else if (*ptr != ',') | |
| { | |
| snprintf (remcomOutBuffer, BUFMAX, | |
| "E.%s:%d: got unexpected character '%c' (0x%x)", | |
| __FILE__, __LINE__, *ptr, *ptr); | |
| putpacket (remcomOutBuffer); | |
| continue; | |
| } | |
| ptr++; | |
| if (*ptr == 'C') | |
| { | |
| asm ("break"); | |
| } | |
| return ret; | |
| } | |
| else | |
| { | |
| handle_packet (ptr, SIGSYS); | |
| } | |
| } | |
| return -1; | |
| } | |
| struct __attribute__ ((packed)) gdb_stat | |
| { | |
| unsigned int st_dev; /* device */ | |
| unsigned int st_ino; /* inode */ | |
| unsigned int st_mode; /* protection */ | |
| unsigned int st_nlink; /* number of hard links */ | |
| unsigned int st_uid; /* user ID of owner */ | |
| unsigned int st_gid; /* group ID of owner */ | |
| unsigned int st_rdev; /* device type (if inode device) */ | |
| unsigned long long st_size; /* total size, in bytes */ | |
| unsigned long long st_blksize; /* blocksize for filesystem I/O */ | |
| unsigned long long st_blocks; /* number of blocks allocated */ | |
| unsigned int st_atime; /* time of last access */ | |
| unsigned int st_mtime; /* time of last modification */ | |
| unsigned int st_ctime; /* time of last change */ | |
| }; | |
| _Static_assert (sizeof (struct gdb_stat) == 64, "Incorrect size for stat"); | |
| static int | |
| gdb_fs_open (struct iob *file, char *path, unsigned flags) | |
| { | |
| int crt = EnterCriticalSection (); | |
| snprintf (remcomOutBuffer, BUFMAX, "Fopen,%x/%x,%x,0", (unsigned int)path, | |
| strlen (path) + 1, | |
| ((flags & 3) - 1) | ((flags & FAPPEND) ? 0x8 : 0) | |
| | (flags & FCREAT) | (flags & FTRUNC)); | |
| putpacket (remcomOutBuffer); | |
| int fd = gdb_fs_syscall (file); | |
| if (fd < 0) | |
| { | |
| if (crt) | |
| ExitCriticalSection (); | |
| return fd; | |
| } | |
| file->i_head = fd; | |
| struct gdb_stat my_stat = { 0 }; | |
| snprintf (remcomOutBuffer, BUFMAX, "Ffstat,%x,%x", fd, | |
| (unsigned int)&my_stat); | |
| putpacket (remcomOutBuffer); | |
| int ret = gdb_fs_syscall (file); | |
| if (ret < 0) | |
| { | |
| if (crt) | |
| ExitCriticalSection (); | |
| return ret; | |
| } | |
| my_stat.st_mode = __builtin_bswap32 (my_stat.st_mode); | |
| my_stat.st_size = __builtin_bswap64 (my_stat.st_size); | |
| if (my_stat.st_mode & 0x2000) // S_IFCHR | |
| file->i_fstype |= DTTYPE_CHAR; | |
| if (my_stat.st_mode & 0x6000) // S_IFBLK | |
| file->i_fstype |= DTTYPE_BLOCK; | |
| if (my_stat.st_mode & 0x8000) // S_IFREG | |
| file->i_fstype |= DTTYPE_FS; | |
| file->i_size = my_stat.st_size; | |
| if (crt) | |
| ExitCriticalSection (); | |
| return 0; | |
| } | |
| static int | |
| gdb_fs_close (struct iob *file) | |
| { | |
| int crt = EnterCriticalSection (); | |
| snprintf (remcomOutBuffer, BUFMAX, "Fclose,%x", (unsigned int)file->i_head); | |
| putpacket (remcomOutBuffer); | |
| int ret = gdb_fs_syscall (file); | |
| if (crt) | |
| ExitCriticalSection (); | |
| return ret; | |
| } | |
| static int | |
| gdb_fs_read (struct iob *file, char *dest, int count) | |
| { | |
| int crt = EnterCriticalSection (); | |
| snprintf (remcomOutBuffer, BUFMAX, "Flseek,%x,%x,0", | |
| (unsigned int)file->i_head, (unsigned int)file->i_offset); | |
| putpacket (remcomOutBuffer); | |
| int pos = gdb_fs_syscall (file); | |
| if (pos < 0) | |
| { | |
| if (crt) | |
| ExitCriticalSection (); | |
| return pos; | |
| } | |
| file->i_offset = pos; | |
| snprintf (remcomOutBuffer, BUFMAX, "Fread,%x,%x,%x", | |
| (unsigned int)file->i_head, (unsigned int)dest, count); | |
| putpacket (remcomOutBuffer); | |
| int n_read = gdb_fs_syscall (file); | |
| if (n_read > 0) | |
| file->i_offset += n_read; | |
| if (crt) | |
| ExitCriticalSection (); | |
| return n_read; | |
| } | |
| static int | |
| gdb_fs_write (struct iob *file, char *src, int count) | |
| { | |
| int crt = EnterCriticalSection (); | |
| snprintf (remcomOutBuffer, BUFMAX, "Flseek,%x,%x,0", | |
| (unsigned int)file->i_head, (unsigned int)file->i_offset); | |
| putpacket (remcomOutBuffer); | |
| int pos = gdb_fs_syscall (file); | |
| if (pos < 0) | |
| { | |
| if (crt) | |
| ExitCriticalSection (); | |
| return pos; | |
| } | |
| file->i_offset = pos; | |
| snprintf (remcomOutBuffer, BUFMAX, "Fwrite,%x,%x,%x", | |
| (unsigned int)file->i_head, (unsigned int)src, count); | |
| putpacket (remcomOutBuffer); | |
| int n_wrote = gdb_fs_syscall (file); | |
| if (n_wrote > 0) | |
| file->i_offset += n_wrote; | |
| if (crt) | |
| ExitCriticalSection (); | |
| return n_wrote; | |
| } | |
| static int | |
| gdb_fs_unlink (struct iob *file, char *path) | |
| { | |
| int crt = EnterCriticalSection (); | |
| snprintf (remcomOutBuffer, BUFMAX, "Funlink,%x/%x", (unsigned int)path, | |
| strlen (path) + 1); | |
| putpacket (remcomOutBuffer); | |
| int ret = gdb_fs_syscall (file); | |
| if (crt) | |
| ExitCriticalSection (); | |
| return ret; | |
| } | |
| static int | |
| gdb_fs_rename (struct iob *old_file, char *old_path, struct iob *new_file, | |
| char *new_path) | |
| { | |
| int crt = EnterCriticalSection (); | |
| snprintf (remcomOutBuffer, BUFMAX, "Frename,%x/%x,%x/%x", | |
| (unsigned int)old_path, strlen (old_path) + 1, | |
| (unsigned int)new_path, strlen (new_path) + 1); | |
| putpacket (remcomOutBuffer); | |
| int ret = gdb_fs_syscall (old_file); | |
| if (crt) | |
| ExitCriticalSection (); | |
| return ret; | |
| } | |
| static const struct device_table gdb_fs_drv = { | |
| .dt_string = "pcdrv", | |
| .dt_type = DTTYPE_FS, | |
| .dt_bsize = 256, | |
| .dt_desc = "GDB", | |
| .dt_init = do_nothing, | |
| .dt_open = (int (*) ())gdb_fs_open, | |
| .dt_strategy = do_nothing, | |
| .dt_close = (int (*) ())gdb_fs_close, | |
| .dt_ioctl = do_nothing, | |
| .dt_read = (int (*) ())gdb_fs_read, | |
| .dt_write = (int (*) ())gdb_fs_write, | |
| .dt_delete = (int (*) ())gdb_fs_unlink, | |
| .dt_undelete = do_nothing, | |
| .dt_firstfile = do_nothing, | |
| .dt_nextfile = do_nothing, | |
| .dt_format = do_nothing, | |
| .dt_cd = do_nothing, | |
| .dt_rename = (int (*) ())gdb_fs_rename, | |
| .dt_remove = do_nothing, | |
| .dt_else = do_nothing, | |
| }; | |
| #endif | |
| /************************************************************************/ | |
| extern void fltr_set_mem_err (); | |
| asm ( | |
| " .globl mem_err\n" | |
| " .bss\n" | |
| " .align 4\n" | |
| " .size mem_err, 4\n" | |
| "mem_err:\n" | |
| " .zero 4\n" | |
| /* Trap handler for memory errors. This just sets mem_err to be non-zero. | |
| * It assumes that EPC is non-zero. This should be safe, as it is doubtful | |
| * that 0 would ever contain code that could mem fault. This routine will | |
| * skip past the faulting instruction after setting mem_err. */ | |
| " .text\n" | |
| " .globl fltr_set_mem_err\n" | |
| "fltr_set_mem_err:\n" | |
| " lui $k0, %hi(mem_err)\n" | |
| " mfc0 $k1, $14\n" | |
| " sw $k1, %lo(mem_err)($k0)\n" | |
| " addi $k1, 4\n" | |
| " jr $k1\n"); | |
| static unsigned long *const exception_vector = (unsigned long *)0x80; | |
| static void | |
| set_mem_fault_trap (int enable) | |
| { | |
| mem_err = 0; | |
| void (*dest) (); | |
| if (enable) | |
| dest = fltr_set_mem_err; | |
| else | |
| dest = GetC0Table ()[0x06]; // ExceptionHandler | |
| exception_vector[0] | |
| = 0x3C1A0000 | (((unsigned long)dest) >> 16); // lui k0, %hi(dest) | |
| exception_vector[1] | |
| = 0x275A0000 | (((unsigned long)dest) & 0xFFFF); // addi k0, %lo(dest) | |
| exception_vector[2] = 0x03400008; // jr k0 | |
| exception_vector[3] = 0x00000000; // +nop | |
| FlushCache (); | |
| } | |
| /* Set up exception handlers for tracing and breakpoints */ | |
| static unsigned int syscall_event, cpu_trap_event, sio_event; | |
| void | |
| set_debug_traps (void) | |
| { | |
| // Install handlers | |
| syscall_event = OpenEvent (HwCPU, EvSpSYSCALL, EvMdINTR, handle_exception); | |
| EnableEvent (syscall_event); | |
| cpu_trap_event = OpenEvent (HwCPU, EvSpTRAP, EvMdINTR, handle_exception); | |
| EnableEvent (cpu_trap_event); | |
| sio_event = OpenEvent (HwSIO, EvSpTRAP, EvMdINTR, sioHandler); | |
| EnableEvent (sio_event); | |
| // Set up serial | |
| *SIO1_CTRL = 0x0040; // Reset | |
| // Unirom defaults: | |
| *SIO1_MODE = 0x00CE; // 8N2 x16 | |
| *SIO1_BAUD = 0x0012; // 115200 x16 | |
| // *SIO1_BAUD = 4; // 500kbaud ("fast") | |
| *SIO1_CTRL = 0x0827; // enable TX, DTR, RX, RTS, and RX interrupt | |
| *I_MASK |= 0x100; // SIO | |
| // Replace DUART TTY driver with our own and enable stdout | |
| A0Table[0x98] = AddTtyDrv; | |
| KernelRedirect (1); | |
| // Install GDB PCDRV driver | |
| AddDrv (&gdb_fs_drv); | |
| // Handle debug register breaks the same as other exceptions | |
| unsigned long *const debug_vector = (unsigned long *)0x40; | |
| debug_vector[0] = 0x08000020; // j 0x80 # jump to exception handler | |
| debug_vector[1] = 0x00000000; // +nop | |
| FlushCache (); | |
| initialized = 1; | |
| } | |
| extern void _start (void); | |
| void | |
| debug_uninit (void) | |
| { | |
| DelDrv (gdb_fs_drv.dt_string); | |
| KernelRedirect (0); | |
| DisableEvent (syscall_event); | |
| CloseEvent (syscall_event); | |
| DisableEvent (cpu_trap_event); | |
| CloseEvent (cpu_trap_event); | |
| DisableEvent (sio_event); | |
| CloseEvent (sio_event); | |
| // _start (); | |
| } | |
| /* This table contains the mapping between MIPS hardware trap types, and | |
| signals, which are primarily what GDB understands. */ | |
| static struct hard_trap_info | |
| { | |
| unsigned char tt; /* Trap type code */ | |
| unsigned char signo; /* Signal that we map this trap into */ | |
| } hard_trap_info[] = { | |
| { 0, SIGINT }, /* external interrupt */ | |
| { 4, SIGSEGV }, /* bad address read */ | |
| { 5, SIGSEGV }, /* bad address write */ | |
| { 6, SIGBUS }, /* instruction bus error */ | |
| { 7, SIGBUS }, /* data bus error */ | |
| { 8, SIGSYS }, /* syscall */ | |
| { 9, SIGTRAP }, /* break */ | |
| { 10, SIGILL }, /* reserved instruction */ | |
| { 11, SIGILL }, /* coprocessor unusable */ | |
| { 12, SIGFPE }, /* overflow */ | |
| { 0, 0 } /* Must be last */ | |
| }; | |
| /* Convert the MIPS hardware trap type code to a UNIX signal number. */ | |
| static int | |
| computeSignal (int tt) | |
| { | |
| struct hard_trap_info *ht; | |
| for (ht = hard_trap_info; ht->signo != 0; ht++) | |
| if (ht->tt == tt) | |
| return ht->signo; | |
| return SIGHUP; /* default for things we don't know about */ | |
| } | |
| /* | |
| * While we find nice hex chars, build an int. | |
| * Return number of chars processed. | |
| */ | |
| static int | |
| hexToInt (char **ptr, int *intValue) | |
| { | |
| int numChars = 0; | |
| int hexValue; | |
| int negative = 0; | |
| *intValue = 0; | |
| while (**ptr) | |
| { | |
| char c = **ptr; | |
| if (c == '-' && !negative) | |
| { | |
| negative = 1; | |
| (*ptr)++; | |
| continue; | |
| } | |
| hexValue = hex (c); | |
| if (hexValue < 0) | |
| break; | |
| *intValue = (*intValue << 4) | hexValue; | |
| numChars++; | |
| (*ptr)++; | |
| } | |
| if (negative) | |
| *intValue *= -1; | |
| return (numChars); | |
| } | |
| /* | |
| * This function does all command procesing for interfacing to gdb. | |
| */ | |
| static long | |
| handle_exception (void) | |
| { | |
| int sigval; | |
| *SIO1_CTRL &= ~0x0020; // disable RTS | |
| struct TCB *task = (*ThreadHeader)->entry; | |
| /* reply to host that an exception has occurred */ | |
| sigval = computeSignal ((task->reg[REG_C0R13_CAUSE] >> 2) & 0x1f); | |
| remcomOutBuffer[1] = hexchars[sigval >> 4]; | |
| remcomOutBuffer[2] = hexchars[sigval & 0xf]; | |
| remcomOutBuffer[3] = 0; | |
| /* break reasons require the vSupported packet | |
| if (((task->reg[REG_C0R13_CAUSE] >> 2) & 0x1f) == 9) | |
| { | |
| /* break instruction or hardware breakpoint / | |
| remcomOutBuffer[0] = 'T'; | |
| if ((cp0dcic & 1) == 0) | |
| { | |
| strcpy(&remcomOutBuffer[3], "swbreak:"); | |
| } | |
| else if (((cp0dcic & 2) != 0) || ((cp0dcic & 32) != 0)) | |
| { | |
| strcpy(&remcomOutBuffer[3], "hwbreak:"); | |
| } | |
| else if ((cp0dcic & 28) != 0) | |
| { | |
| int bda = cp0bda; | |
| if ((cp0dcic & 4) != 0) | |
| { | |
| strcpy(&remcomOutBuffer[3], "awatch:"); | |
| mem2hex((unsigned char *)&bda, &remcomOutBuffer[10], sizeof(int), 0); | |
| } | |
| else if ((cp0dcic & 8) != 0) | |
| { | |
| strcpy(&remcomOutBuffer[3], "rwatch:"); | |
| mem2hex((unsigned char *)&bda, &remcomOutBuffer[10], sizeof(int), 0); | |
| } | |
| else if ((cp0dcic & 16) != 0) | |
| { | |
| strcpy(&remcomOutBuffer[3], "watch:"); | |
| mem2hex((unsigned char *)&bda, &remcomOutBuffer[9], sizeof(int), 0); | |
| } | |
| } | |
| } | |
| else*/ | |
| { | |
| remcomOutBuffer[0] = 'S'; | |
| } | |
| putpacket (remcomOutBuffer); | |
| /* reset DCIC after single-step */ | |
| cp0dcic = 0; | |
| while (1) | |
| { | |
| handle_packet (getpacket (), sigval); | |
| } | |
| return 0; | |
| } | |
| static void | |
| handle_packet (char *ptr, int sigval) | |
| { | |
| int addr; | |
| int length; | |
| struct TCB *task = (*ThreadHeader)->entry; | |
| unsigned int actual_pc | |
| = task->reg[REG_C0R14_EPC] | |
| + ((task->reg[REG_C0R13_CAUSE] & 0x80000000) == 0 ? 0 : 4); | |
| remcomOutBuffer[0] = 0; | |
| switch (*ptr++) | |
| { | |
| case '?': /* query the reason the target halted */ | |
| remcomOutBuffer[0] = 'S'; | |
| remcomOutBuffer[1] = hexchars[sigval >> 4]; | |
| remcomOutBuffer[2] = hexchars[sigval & 0xf]; | |
| remcomOutBuffer[3] = 0; | |
| break; | |
| case 'g': /* return the value of the CPU registers */ | |
| { | |
| ptr = remcomOutBuffer; | |
| ptr = mem2hex ((unsigned char *)task->reg, ptr, 32 * 4, 0); | |
| ptr = mem2hex ((unsigned char *)&task->reg[REG_C0R12_SR], ptr, 4, 0); | |
| ptr = mem2hex ((unsigned char *)&task->reg[REG_MDLO], ptr, 4, 0); | |
| ptr = mem2hex ((unsigned char *)&task->reg[REG_MDHI], ptr, 4, 0); | |
| int badVAddr = 0; | |
| asm ("mfc0 %0, $8" : "=r"(badVAddr)); // must use assembly here, otherwise compiles to swc0 | |
| ptr = mem2hex ((unsigned char *)&badVAddr, ptr, 4, 0); | |
| ptr = mem2hex ((unsigned char *)&task->reg[REG_C0R13_CAUSE], ptr, 4, | |
| 0); | |
| /* unsure whether to adjust PC based on Cause.BD... */ | |
| ptr = mem2hex ((unsigned char *)&actual_pc, ptr, 4, 0); | |
| memset (ptr, 'x', 32 * 4 * 2); /* Floating point */ | |
| ptr += 32 * 4 * 2; | |
| *ptr = 0; | |
| ptr++; | |
| } // maybe check if 'g' response matches the registers from emulator | |
| break; | |
| case 'G': /* set the value of the CPU registers - return OK */ | |
| { | |
| hex2mem (ptr, (unsigned char *)task->reg, 32 * 4, 0); | |
| hex2mem (ptr + 32 * 4 * 2, (unsigned char *)&task->reg[REG_C0R12_SR], | |
| 4, 0); | |
| hex2mem (ptr + 33 * 4 * 2, (unsigned char *)&task->reg[REG_MDLO], 4, | |
| 0); | |
| hex2mem (ptr + 34 * 4 * 2, (unsigned char *)&task->reg[REG_MDHI], 4, | |
| 0); | |
| hex2mem (ptr + 36 * 4 * 2, | |
| (unsigned char *)&task->reg[REG_C0R13_CAUSE], 4, 0); | |
| hex2mem (ptr + 37 * 4 * 2, (unsigned char *)&task->reg[REG_C0R14_EPC], | |
| 4, 0); | |
| strcpy (remcomOutBuffer, "OK"); | |
| } | |
| break; | |
| case 'k': /* kill request */ | |
| /* _boot assumes we booted from CD already */ | |
| /* _boot(); */ | |
| case 'r': /* reset the entire system */ | |
| /* (apparently never actually sent by GDB) */ | |
| ((void (*) (void))0xBFC00000) (); | |
| break; | |
| case 'm': /* mAA..AA,LLLL Read LLLL bytes at address AA..AA */ | |
| /* Try to read %x,%x. */ | |
| if (hexToInt (&ptr, &addr) && *ptr++ == ',' && hexToInt (&ptr, &length)) | |
| { | |
| if (mem2hex ((unsigned char *)addr, remcomOutBuffer, length, 1)) | |
| break; | |
| strcpy (remcomOutBuffer, "E01"); | |
| } | |
| else | |
| strcpy (remcomOutBuffer, "E02"); | |
| break; | |
| case 'M': /* MAA..AA,LLLL: Write LLLL bytes at address AA.AA return OK */ | |
| /* Try to read '%x,%x:'. */ | |
| if (hexToInt (&ptr, &addr) && *ptr++ == ',' && hexToInt (&ptr, &length) | |
| && *ptr++ == ':') | |
| { | |
| if (hex2mem (ptr, (unsigned char *)addr, length, 1)) | |
| strcpy (remcomOutBuffer, "OK"); | |
| else | |
| strcpy (remcomOutBuffer, "E03"); | |
| } | |
| else | |
| strcpy (remcomOutBuffer, "E04"); | |
| break; | |
| case 'C': /* continue with signal - ignore signal parameter */ | |
| if (hexToInt (&ptr, &addr)) | |
| { | |
| if (*ptr == ';') | |
| ptr++; | |
| } | |
| else | |
| strcpy (remcomOutBuffer, "E05"); | |
| case 'c': /* cAA..AA Continue at address AA..AA(optional) */ | |
| /* try to read optional parameter, pc unchanged if no parm */ | |
| if (hexToInt (&ptr, &addr)) | |
| { | |
| task->reg[REG_C0R14_EPC] = addr; | |
| } | |
| /* if the trapping instruction was a "break," skip over it */ | |
| else if (*(int *)(task->reg[REG_C0R14_EPC]) == 0x0000000D) | |
| { | |
| task->reg[REG_C0R14_EPC] += 4; | |
| } | |
| *SIO1_CTRL |= 0x0020; // re-enable RTS | |
| /* Need to flush the instruction cache here, as we may have deposited a | |
| breakpoint, and the icache probably has no way of knowing that a data ref | |
| to some location may have changed something that is in the instruction | |
| cache. | |
| */ | |
| FlushCache (); | |
| ReturnFromException (); | |
| break; | |
| case 'S': /* step with signal - ignore signal parameter */ | |
| if (hexToInt (&ptr, &addr)) | |
| { | |
| if (*ptr == ';') | |
| ptr++; | |
| } | |
| else | |
| strcpy (remcomOutBuffer, "E06"); | |
| case 's': /* sAA..AA Single step at address AA..AA (optional) */ | |
| /* try to read optional parameter, pc unchanged if no parm */ | |
| if (hexToInt (&ptr, &addr)) | |
| { | |
| task->reg[REG_C0R14_EPC] = addr; | |
| /* break on next instruction */ | |
| cp0bpc = task->reg[REG_C0R14_EPC] + 4; | |
| } | |
| else | |
| { | |
| /* break on next instruction */ | |
| if ((task->reg[REG_C0R13_CAUSE] & 0xC0000000) == 0xC0000000) | |
| /* branch will be taken, break on target address */ | |
| cp0bpc = cp0tar; | |
| else | |
| cp0bpc = actual_pc + 4; | |
| /* if the trapping instruction was a "break," skip over it */ | |
| if (*(int *)(task->reg[REG_C0R14_EPC]) == 0x0000000D) | |
| { | |
| task->reg[REG_C0R14_EPC] += 4; | |
| } | |
| } | |
| cp0bpcm = 0x1FFFFFFF; // match all memory regions | |
| cp0dcic = 0xE1800000; // enable execution breakpoints | |
| *SIO1_CTRL |= 0x0020; // re-enable RTS | |
| ReturnFromException (); | |
| break; | |
| } /* switch */ | |
| /* reply to the request */ | |
| putpacket (remcomOutBuffer); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment