Skip to content

Instantly share code, notes, and snippets.

@impiaaa
Last active July 30, 2025 06:05
Show Gist options
  • Select an option

  • Save impiaaa/a523d49fe04c9e5c0c610ff2cfdc139e to your computer and use it in GitHub Desktop.

Select an option

Save impiaaa/a523d49fe04c9e5c0c610ff2cfdc139e to your computer and use it in GitHub Desktop.
GDB stub for PlayStation 1
/****************************************************************************
*
* 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