diff options
Diffstat (limited to 'bsps/mips/shared/gdbstub/mips-stub.c')
-rw-r--r-- | bsps/mips/shared/gdbstub/mips-stub.c | 1589 |
1 files changed, 1589 insertions, 0 deletions
diff --git a/bsps/mips/shared/gdbstub/mips-stub.c b/bsps/mips/shared/gdbstub/mips-stub.c new file mode 100644 index 0000000000..8320eb66a9 --- /dev/null +++ b/bsps/mips/shared/gdbstub/mips-stub.c @@ -0,0 +1,1589 @@ +#define GDB_STUB_ENABLE_THREAD_SUPPORT 1 +/******************************************************************************* + + THIS SOFTWARE IS NOT COPYRIGHTED + + The following software is offered for use in the public domain. + There is no warranty with regard to this software or its performance + and the user must accept the software "AS IS" with all faults. + + THE CONTRIBUTORS DISCLAIM ANY WARRANTIES, EXPRESS OR IMPLIED, WITH + REGARD TO THIS SOFTWARE INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +******************************************************************************** +* +* r46kstub.c -- target debugging stub for the IDT R4600 Orion processor +* +* This module is based on the stub for the Hitachi SH processor written by +* Ben Lee and Steve Chamberlain and supplied with gdb 4.16. The latter +* in turn "is originally based on an m68k software stub written by Glenn +* Engel at HP, but has changed quite a bit." The changes for the R4600 +* were written by C. M. Heard at VVNET. They were based in part on the +* Algorithmics R4000 version of Phil Bunce's PMON program. +* +* Remote communication protocol: +* +* A debug packet whose contents are <data> +* is encapsulated for transmission in the form: +* +* $ <data> # CSUM1 CSUM2 +* +* <data> must be ASCII alphanumeric and cannot include characters +* '$' or '#'. If <data> starts with two characters followed by +* ':', then the existing stubs interpret this as a sequence number. +* +* CSUM1 and CSUM2 are ascii hex representation of an 8-bit +* checksum of <data>, the most significant nibble is sent first. +* the hex digits 0-9,a-f are used. +* +* Receiver responds with: +* +* + if CSUM is correct +* - if CSUM is incorrect +* +* <data> is as follows. All values are encoded in ascii hex digits. +* +* Request Packet +* +* read registers g +* reply XX....X Each byte of register data +* is described by two hex digits. +* Registers are in the internal order +* for GDB, and the bytes in a register +* are in the same order the machine uses. +* or ENN for an error. +* +* write regs GXX..XX Each byte of register data +* is described by two hex digits. +* reply OK for success +* ENN for an error +* +* write reg Pn...=r... Write register n... with value r.... +* reply OK for success +* ENN for an error +* +* read mem mAA..AA,LLLL AA..AA is address, LLLL is length. +* reply XX..XX XX..XX is mem contents +* Can be fewer bytes than requested +* if able to read only part of the data. +* or ENN NN is errno +* +* write mem MAA..AA,LLLL:XX..XX +* AA..AA is address, +* LLLL is number of bytes, +* XX..XX is data +* reply OK for success +* ENN for an error (this includes the case +* where only part of the data was +* written). +* +* cont cAA..AA AA..AA is address to resume +* If AA..AA is omitted, +* resume at same address. +* +* step sAA..AA AA..AA is address to resume +* If AA..AA is omitted, +* resume at same address. +* +* There is no immediate reply to step or cont. +* The reply comes when the machine stops. +* It is SAA AA is the "signal number" +* +* last signal ? Reply with the reason for stopping. +* This is the same reply as is generated +* for step or cont: SAA where AA is the +* signal number. +* +* detach D Host is detaching. Reply OK and +* end remote debugging session. +* +* reserved <other> On other requests, the stub should +* ignore the request and send an empty +* response ($#<checksum>). This way +* we can extend the protocol and GDB +* can tell whether the stub it is +* talking to uses the old or the new. +* +* Responses can be run-length encoded to save space. A '*' means that +* the next character is an ASCII encoding giving a repeat count which +* stands for that many repetitions of the character preceding the '*'. +* The encoding is n+29, yielding a printable character when n >=3 +* (which is where rle starts to win). Don't use n > 99 since gdb +* masks each character is receives with 0x7f in order to strip off +* the parity bit. +* +* As an example, "0* " means the same thing as "0000". +* +*******************************************************************************/ + + +#include <string.h> +#include <signal.h> +#include "mips_opcode.h" +/* #include "memlimits.h" */ +#include <rtems.h> +#include <rtems/bspIo.h> +#include "gdb_if.h" + +/* Change it to something meaningful when debugging */ +#undef ASSERT +#define ASSERT(x) if(!(x)) printk("ASSERT: stub: %d\n", __LINE__) + +/***************/ +/* Exception Codes */ +#define EXC_INT 0 /* External interrupt */ +#define EXC_MOD 1 /* TLB modification exception */ +#define EXC_TLBL 2 /* TLB miss (Load or Ifetch) */ +#define EXC_TLBS 3 /* TLB miss (Store) */ +#define EXC_ADEL 4 /* Address error (Load or Ifetch) */ +#define EXC_ADES 5 /* Address error (Store) */ +#define EXC_IBE 6 /* Bus error (Ifetch) */ +#define EXC_DBE 7 /* Bus error (data load or store) */ +#define EXC_SYS 8 /* System call */ +#define EXC_BP 9 /* Break point */ +#define EXC_RI 10 /* Reserved instruction */ +#define EXC_CPU 11 /* Coprocessor unusable */ +#define EXC_OVF 12 /* Arithmetic overflow */ +#define EXC_TRAP 13 /* Trap exception */ +#define EXC_FPE 15 /* Floating Point Exception */ + +/* FPU Control/Status register fields */ +#define CSR_FS 0x01000000 /* Set to flush denormals to zero */ +#define CSR_C 0x00800000 /* Condition bit (set by FP compare) */ + +#define CSR_CMASK (0x3f<<12) +#define CSR_CE 0x00020000 +#define CSR_CV 0x00010000 +#define CSR_CZ 0x00008000 +#define CSR_CO 0x00004000 +#define CSR_CU 0x00002000 +#define CSR_CI 0x00001000 + +#define CSR_EMASK (0x1f<<7) +#define CSR_EV 0x00000800 +#define CSR_EZ 0x00000400 +#define CSR_EO 0x00000200 +#define CSR_EU 0x00000100 +#define CSR_EI 0x00000080 + +#define CSR_FMASK (0x1f<<2) +#define CSR_FV 0x00000040 +#define CSR_FZ 0x00000020 +#define CSR_FO 0x00000010 +#define CSR_FU 0x00000008 +#define CSR_FI 0x00000004 + +#define CSR_RMODE_MASK (0x3<<0) +#define CSR_RM 0x00000003 +#define CSR_RP 0x00000002 +#define CSR_RZ 0x00000001 +#define CSR_RN 0x00000000 + +/***************/ + +/* + * Saved register information. Must be prepared by the exception + * preprocessor before handle_exception is invoked. + */ +#if (__mips == 3) +typedef long long mips_register_t; +#define R_SZ 8 +#elif (__mips == 1) +typedef unsigned int mips_register_t; +#define R_SZ 4 +#else +#error "unknown MIPS ISA" +#endif +static mips_register_t *registers; + +#if defined(GDB_STUB_ENABLE_THREAD_SUPPORT) +static char do_threads; /* != 0 means we are supporting threads */ +#endif + +/* + * The following external functions provide character input and output. + */ +extern char getDebugChar (void); +extern void putDebugChar (char); + +/* + * Exception handler + */ +void handle_exception (rtems_vector_number vector, CPU_Interrupt_frame *frame); + +/* + * Prototype needed by this code and to keep it self contained. + */ +void rtems_interrupt_catch( rtems_isr_entry, int, rtems_isr_entry *); + +/* + * The following definitions are used for the gdb stub memory map + */ +struct memseg +{ + unsigned begin, end, opts; +}; + +static int is_readable(unsigned,unsigned); +static int is_writeable(unsigned,unsigned); +static int is_steppable(unsigned); + +/* + * BUFMAX defines the maximum number of characters in the inbound & outbound + * packet buffers. At least 4+(sizeof registers)*2 bytes will be needed for + * register packets. Memory dump packets can profitably use even more. + */ +#define BUFMAX 1500 + +static char inBuffer[BUFMAX]; +static char outBuffer[BUFMAX]; + +/* Structure to keep info on a z-breaks */ +#define BREAKNUM 32 + +struct z0break +{ + /* List support */ + struct z0break *next; + struct z0break *prev; + + /* Location, preserved data */ + + /* the address pointer, really, really must be a pointer to + ** a 32 bit quantity (likely 64 on the R4k), so the full instruction is read & + ** written. Making it a char * as on the i386 will cause + ** the zbreaks to mess up the breakpoint instructions + */ + unsigned char *address; + unsigned instr; +}; + +static struct z0break z0break_arr[BREAKNUM]; +static struct z0break *z0break_avail = NULL; +static struct z0break *z0break_list = NULL; + + +/* + * Convert an int to hex. + */ +const char gdb_hexchars[] = "0123456789abcdef"; + +#define highhex(x) gdb_hexchars [(x >> 4) & 0xf] +#define lowhex(x) gdb_hexchars [x & 0xf] + +/* + * Convert length bytes of data starting at addr into hex, placing the + * result in buf. Return a pointer to the last (null) char in buf. + */ +static char * +mem2hex (void *_addr, int length, char *buf) +{ + unsigned int addr = (unsigned int) _addr; + + if (((addr & 0x7) == 0) && ((length & 0x7) == 0)) /* dword aligned */ + { + long long *source = (long long *) (addr); + long long *limit = (long long *) (addr + length); + + while (source < limit) + { + int i; + long long k = *source++; + + for (i = 15; i >= 0; i--) + *buf++ = gdb_hexchars [(k >> (i*4)) & 0xf]; + } + } + else if (((addr & 0x3) == 0) && ((length & 0x3) == 0)) /* word aligned */ + { + int *source = (int *) (addr); + int *limit = (int *) (addr + length); + + while (source < limit) + { + int i; + int k = *source++; + + for (i = 7; i >= 0; i--) + *buf++ = gdb_hexchars [(k >> (i*4)) & 0xf]; + } + } + else if (((addr & 0x1) == 0) && ((length & 0x1) == 0)) /* halfword aligned */ + { + short *source = (short *) (addr); + short *limit = (short *) (addr + length); + + while (source < limit) + { + int i; + short k = *source++; + + for (i = 3; i >= 0; i--) + *buf++ = gdb_hexchars [(k >> (i*4)) & 0xf]; + } + } + else /* byte aligned */ + { + char *source = (char *) (addr); + char *limit = (char *) (addr + length); + + while (source < limit) + { + int i; + char k = *source++; + + for (i = 1; i >= 0; i--) + *buf++ = gdb_hexchars [(k >> (i*4)) & 0xf]; + } + } + + *buf = '\0'; + return (buf); +} + + +/* + * Convert a hex character 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); +} + +/* + * Convert a string from hex to int until a non-hex digit + * is found. Return the number of characters processed. + */ +static int +hexToInt (char **ptr, int *intValue) +{ + int numChars = 0; + int hexValue; + + *intValue = 0; + + while (**ptr) + { + hexValue = hex (**ptr); + if (hexValue >= 0) + { + *intValue = (*intValue << 4) | hexValue; + numChars++; + } + else + break; + + (*ptr)++; + } + + return (numChars); +} + +/* + * Convert a string from hex to long long until a non-hex + * digit is found. Return the number of characters processed. + */ +static int +hexToLongLong (char **ptr, long long *intValue) +{ + int numChars = 0; + int hexValue; + + *intValue = 0; + + while (**ptr) + { + hexValue = hex (**ptr); + if (hexValue >= 0) + { + *intValue = (*intValue << 4) | hexValue; + numChars++; + } + else + break; + + (*ptr)++; + } + + return (numChars); +} + +/* + * Convert the hex array buf into binary, placing the result at the + * specified address. If the conversion fails at any point (i.e., + * if fewer bytes are written than indicated by the size parameter) + * then return 0; otherwise return 1. + */ +static int +hex2mem (char *buf, void *_addr, int length) +{ + unsigned int addr = (unsigned int) _addr; + if (((addr & 0x7) == 0) && ((length & 0x7) == 0)) /* dword aligned */ + { + long long *target = (long long *) (addr); + long long *limit = (long long *) (addr + length); + + while (target < limit) + { + int i, j; + long long k = 0; + + for (i = 0; i < 16; i++) + if ((j = hex(*buf++)) < 0) + return 0; + else + k = (k << 4) + j; + *target++ = k; + } + } + else if (((addr & 0x3) == 0) && ((length & 0x3) == 0)) /* word aligned */ + { + int *target = (int *) (addr); + int *limit = (int *) (addr + length); + + while (target < limit) + { + int i, j; + int k = 0; + + for (i = 0; i < 8; i++) + if ((j = hex(*buf++)) < 0) + return 0; + else + k = (k << 4) + j; + *target++ = k; + } + } + else if (((addr & 0x1) == 0) && ((length & 0x1) == 0)) /* halfword aligned */ + { + short *target = (short *) (addr); + short *limit = (short *) (addr + length); + + while (target < limit) + { + int i, j; + short k = 0; + + for (i = 0; i < 4; i++) + if ((j = hex(*buf++)) < 0) + return 0; + else + k = (k << 4) + j; + *target++ = k; + } + } + else /* byte aligned */ + { + char *target = (char *) (addr); + char *limit = (char *) (addr + length); + + while (target < limit) + { + int i, j; + char k = 0; + + for (i = 0; i < 2; i++) + if ((j = hex(*buf++)) < 0) + return 0; + else + k = (k << 4) + j; + *target++ = k; + } + } + + return 1; +} + +/* Convert the binary stream in BUF to memory. + + Gdb will escape $, #, and the escape char (0x7d). + COUNT is the total number of bytes to write into + memory. */ +static unsigned char * +bin2mem ( + char *buf, + unsigned char *mem, + int count +) +{ + int i; + + for (i = 0; i < count; i++) { + /* Check for any escaped characters. Be paranoid and + only unescape chars that should be escaped. */ + if (*buf == 0x7d) { + switch (*(buf+1)) { + case 0x3: /* # */ + case 0x4: /* $ */ + case 0x5d: /* escape char */ + buf++; + *buf |= 0x20; + break; + default: + /* nothing */ + break; + } + } + + *mem++ = *buf++; + } + + return mem; +} + + +/* + * Scan the input stream for a sequence for the form $<data>#<checksum>. + */ +static void +getpacket (char *buffer) +{ + unsigned char checksum; + unsigned char xmitcsum; + int i; + int count; + char ch; + do + { + /* wait around for the start character, ignore all other characters */ + while ((ch = getDebugChar ()) != '$'); + checksum = 0; + xmitcsum = -1; + + count = 0; + + /* now, read until a # or end of buffer is found */ + while ( (count < BUFMAX-1) && ((ch = getDebugChar ()) != '#') ) + checksum += (buffer[count++] = ch); + + /* make sure that the buffer is null-terminated */ + 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]); + /* remove sequence chars from buffer */ + for (i = 3; i <= count; i++) + buffer[i - 3] = buffer[i]; + } + } + } + } + while (checksum != xmitcsum); +} + +/* + * Get a positive/negative acknowledgment for a transmitted packet. + */ +static char +getAck (void) +{ + char c; + + do + { + c = getDebugChar (); + } + while ((c != '+') && (c != '-')); + + return c; +} + +/* + * Send the packet in buffer and wait for a positive acknowledgement. + */ +static void +putpacket (char *buffer) +{ + int checksum; + + /* $<packet info>#<checksum> */ + do + { + char *src = buffer; + putDebugChar ('$'); + checksum = 0; + + while (*src != '\0') + { + int runlen = 0; + + /* Do run length encoding */ + while ((src[runlen] == src[0]) && (runlen < 99)) + runlen++; + if (runlen > 3) + { + int encode; + /* Got a useful amount */ + putDebugChar (*src); + checksum += *src; + putDebugChar ('*'); + checksum += '*'; + checksum += (encode = (runlen - 4) + ' '); + putDebugChar (encode); + src += runlen; + } + else + { + putDebugChar (*src); + checksum += *src; + src++; + } + } + + putDebugChar ('#'); + putDebugChar (highhex (checksum)); + putDebugChar (lowhex (checksum)); + } + while (getAck () != '+'); +} + + +/* + * Saved instruction data for single step support + */ +static struct + { + unsigned *targetAddr; + unsigned savedInstr; + } +instrBuffer; + +/* + * If a step breakpoint was planted restore the saved instruction. + */ +static void +undoSStep (void) +{ + if (instrBuffer.targetAddr != NULL) + { + *instrBuffer.targetAddr = instrBuffer.savedInstr; + instrBuffer.targetAddr = NULL; + } + instrBuffer.savedInstr = NOP_INSTR; +} + +/* + * If a single step is requested put a temporary breakpoint at the instruction + * which logically follows the next one to be executed. If the next instruction + * is a branch instruction then skip the instruction in the delay slot. NOTE: + * ERET instructions are NOT handled, as it is impossible to single-step through + * the exit code in an exception handler. In addition, no attempt is made to + * do anything about BC0T and BC0F, since a condition bit for coprocessor 0 + * is not defined on the R4600. Finally, BC2T and BC2F are ignored since there + * is no coprocessor 2 on a 4600. + */ +static void +doSStep (void) +{ + InstFmt inst; + + instrBuffer.targetAddr = (unsigned *)(registers[PC]+4); /* set default */ + + inst.word = *(unsigned *)registers[PC]; /* read the next instruction */ + + switch (inst.RType.op) { /* override default if branch */ + case OP_SPECIAL: + switch (inst.RType.func) { + case OP_JR: + case OP_JALR: + instrBuffer.targetAddr = + (unsigned *)registers[inst.RType.rs]; + break; + }; + break; + + case OP_REGIMM: + switch (inst.IType.rt) { + case OP_BLTZ: + case OP_BLTZL: + case OP_BLTZAL: + case OP_BLTZALL: + if (registers[inst.IType.rs] < 0 ) + instrBuffer.targetAddr = + (unsigned *)(((signed short)inst.IType.imm<<2) + + (registers[PC]+4)); + else + instrBuffer.targetAddr = (unsigned*)(registers[PC]+8); + break; + case OP_BGEZ: + case OP_BGEZL: + case OP_BGEZAL: + case OP_BGEZALL: + if (registers[inst.IType.rs] >= 0 ) + instrBuffer.targetAddr = + (unsigned *)(((signed short)inst.IType.imm<<2) + + (registers[PC]+4)); + else + instrBuffer.targetAddr = (unsigned*)(registers[PC]+8); + break; + }; + break; + + case OP_J: + case OP_JAL: + instrBuffer.targetAddr = + (unsigned *)((inst.JType.target<<2) + ((registers[PC]+4)&0xf0000000)); + break; + + case OP_BEQ: + case OP_BEQL: + if (registers[inst.IType.rs] == registers[inst.IType.rt]) + instrBuffer.targetAddr = + (unsigned *)(((signed short)inst.IType.imm<<2) + (registers[PC]+4)); + else + instrBuffer.targetAddr = (unsigned*)(registers[PC]+8); + break; + case OP_BNE: + case OP_BNEL: + if (registers[inst.IType.rs] != registers[inst.IType.rt]) + instrBuffer.targetAddr = + (unsigned *)(((signed short)inst.IType.imm<<2) + (registers[PC]+4)); + else + instrBuffer.targetAddr = (unsigned*)(registers[PC]+8); + break; + case OP_BLEZ: + case OP_BLEZL: + if (registers[inst.IType.rs] <= 0) + instrBuffer.targetAddr = + (unsigned *)(((signed short)inst.IType.imm<<2) + (registers[PC]+4)); + else + instrBuffer.targetAddr = (unsigned*)(registers[PC]+8); + break; + case OP_BGTZ: + case OP_BGTZL: + if (registers[inst.IType.rs] > 0) + instrBuffer.targetAddr = + (unsigned *)(((signed short)inst.IType.imm<<2) + (registers[PC]+4)); + else + instrBuffer.targetAddr = (unsigned*)(registers[PC]+8); + break; + + case OP_COP1: + if (inst.RType.rs == OP_BC) + switch (inst.RType.rt) { + case COPz_BCF: + case COPz_BCFL: + if (registers[FCSR] & CSR_C) + instrBuffer.targetAddr = (unsigned*)(registers[PC]+8); + else + instrBuffer.targetAddr = + (unsigned *)(((signed short)inst.IType.imm<<2) + + (registers[PC]+4)); + break; + case COPz_BCT: + case COPz_BCTL: + if (registers[FCSR] & CSR_C) + instrBuffer.targetAddr = + (unsigned *)(((signed short)inst.IType.imm<<2) + + (registers[PC]+4)); + else + instrBuffer.targetAddr = (unsigned*)(registers[PC]+8); + break; + }; + break; + } + + if( is_steppable((unsigned)instrBuffer.targetAddr) && *(instrBuffer.targetAddr) != BREAK_INSTR ) + { + instrBuffer.savedInstr = *instrBuffer.targetAddr; + *instrBuffer.targetAddr = BREAK_INSTR; + } + else + { + instrBuffer.targetAddr = NULL; + instrBuffer.savedInstr = NOP_INSTR; + } + return; +} + + +/* + * Translate the R4600 exception code into a Unix-compatible signal. + */ +static int +computeSignal (void) +{ + int exceptionCode = (registers[CAUSE] & CAUSE_EXCMASK) >> CAUSE_EXCSHIFT; + + switch (exceptionCode) + { + case EXC_INT: + /* External interrupt */ + return SIGINT; + + case EXC_RI: + /* Reserved instruction */ + case EXC_CPU: + /* Coprocessor unusable */ + return SIGILL; + + case EXC_BP: + /* Break point */ + return SIGTRAP; + + case EXC_OVF: + /* Arithmetic overflow */ + case EXC_TRAP: + /* Trap exception */ + case EXC_FPE: + /* Floating Point Exception */ + return SIGFPE; + + case EXC_IBE: + /* Bus error (Ifetch) */ + case EXC_DBE: + /* Bus error (data load or store) */ + return SIGBUS; + + case EXC_MOD: + /* TLB modification exception */ + case EXC_TLBL: + /* TLB miss (Load or Ifetch) */ + case EXC_TLBS: + /* TLB miss (Store) */ + case EXC_ADEL: + /* Address error (Load or Ifetch) */ + case EXC_ADES: + /* Address error (Store) */ + return SIGSEGV; + + case EXC_SYS: + /* System call */ + return SIGSYS; + + default: + return SIGTERM; + } +} + +/* + * This support function prepares and sends the message containing the + * basic information about this exception. + */ +static void gdb_stub_report_exception_info( + rtems_vector_number vector, + CPU_Interrupt_frame *frame, + int thread +) +{ + char *optr; + int sigval; + + optr = outBuffer; + *optr++ = 'T'; + sigval = computeSignal (); + *optr++ = highhex (sigval); + *optr++ = lowhex (sigval); + + *optr++ = highhex(SP); /*gdb_hexchars[SP]; */ + *optr++ = lowhex(SP); + *optr++ = ':'; + optr = mem2hstr(optr, (unsigned char *)&frame->sp, R_SZ ); + *optr++ = ';'; + + *optr++ = highhex(PC); /*gdb_hexchars[PC]; */ + *optr++ = lowhex(PC); + *optr++ = ':'; + optr = mem2hstr(optr, (unsigned char *)&frame->epc, R_SZ ); + *optr++ = ';'; + +#if defined(GDB_STUB_ENABLE_THREAD_SUPPORT) + if (do_threads) + { + *optr++ = 't'; + *optr++ = 'h'; + *optr++ = 'r'; + *optr++ = 'e'; + *optr++ = 'a'; + *optr++ = 'd'; + *optr++ = ':'; + optr = thread2vhstr(optr, thread); + *optr++ = ';'; + } +#endif + *optr++ = '\0'; +} + + + +/* + * Scratch frame used to retrieve contexts for different threads, so as + * not to disrupt our current context on the stack + */ +CPU_Interrupt_frame current_thread_registers; + +/* + * This function handles all exceptions. It only does two things: + * it figures out why it was activated and tells gdb, and then it + * reacts to gdb's requests. + */ + +extern void clear_cache(void); +void handle_exception (rtems_vector_number vector, CPU_Interrupt_frame *frame) +{ + int host_has_detached = 0; + int regno, addr, length; + char *ptr; + int current_thread; /* current generic thread */ + int thread; /* stopped thread: context exception happened in */ + + long long regval; + void *regptr; + int binary; + + registers = (mips_register_t *)frame; + + thread = 0; +#if defined(GDB_STUB_ENABLE_THREAD_SUPPORT) + if (do_threads) { + thread = rtems_gdb_stub_get_current_thread(); + } +#endif + current_thread = thread; + + { + /* reapply all breakpoints regardless of how we came in */ + struct z0break *z0, *zother; + + for (zother=z0break_list; zother!=NULL; zother=zother->next) + { + if( zother->instr == 0xffffffff ) + { + /* grab the instruction */ + zother->instr = *(zother->address); + /* and insert the breakpoint */ + *(zother->address) = BREAK_INSTR; + } + } + + /* see if we're coming from a breakpoint */ + if( *((unsigned *)frame->epc) == BREAK_INSTR ) + { + /* see if its one of our zbreaks */ + for (z0=z0break_list; z0!=NULL; z0=z0->next) + { + if( (unsigned)z0->address == frame->epc) + break; + } + if( z0 ) + { + /* restore the original instruction */ + *(z0->address) = z0->instr; + /* flag the breakpoint */ + z0->instr = 0xffffffff; + + /* + now when we return, we'll execute the original code in + the original state. This leaves our breakpoint inactive + since the break instruction isn't there, but we'll reapply + it the next time we come in via step or breakpoint + */ + } + else + { + /* not a zbreak, see if its our trusty stepping code */ + + /* + * Restore the saved instruction at + * the single-step target address. + */ + undoSStep(); + } + } + } + + /* reply to host that an exception has occurred with some basic info */ + gdb_stub_report_exception_info(vector, frame, thread); + putpacket (outBuffer); + + while (!(host_has_detached)) { + outBuffer[0] = '\0'; + getpacket (inBuffer); + binary = 0; + + switch (inBuffer[0]) { + case '?': + gdb_stub_report_exception_info(vector, frame, thread); + break; + + case 'd': /* toggle debug flag */ + /* can print ill-formed commands in valid packets & checksum errors */ + break; + + case 'D': + /* remote system is detaching - return OK and exit from debugger */ + strcpy (outBuffer, "OK"); + host_has_detached = 1; + break; + + case 'g': /* return the values of the CPU registers */ + regptr = registers; +#if defined(GDB_STUB_ENABLE_THREAD_SUPPORT) + if (do_threads && current_thread != thread ) + regptr = ¤t_thread_registers; +#endif + mem2hex (regptr, NUM_REGS * (sizeof registers), outBuffer); + break; + + case 'G': /* set the values of the CPU registers - return OK */ + regptr = registers; +#if defined(GDB_STUB_ENABLE_THREAD_SUPPORT) + if (do_threads && current_thread != thread ) + regptr = ¤t_thread_registers; +#endif + if (hex2mem (&inBuffer[1], regptr, NUM_REGS * (sizeof registers))) + strcpy (outBuffer, "OK"); + else + strcpy (outBuffer, "E00"); /* E00 = bad "set register" command */ + break; + + case 'P': + /* Pn...=r... Write register n... with value r... - return OK */ + ptr = &inBuffer[1]; + if (hexToInt(&ptr, ®no) && + *ptr++ == '=' && + hexToLongLong(&ptr, ®val)) + { + registers[regno] = regval; + strcpy (outBuffer, "OK"); + } + else + strcpy (outBuffer, "E00"); /* E00 = bad "set register" command */ + break; + + case 'm': + /* mAA..AA,LLLL Read LLLL bytes at address AA..AA */ + ptr = &inBuffer[1]; + if (hexToInt (&ptr, &addr) + && *ptr++ == ',' + && hexToInt (&ptr, &length) + && is_readable (addr, length) + && (length < (BUFMAX - 4)/2)) + mem2hex ((void *)addr, length, outBuffer); + else + strcpy (outBuffer, "E01"); /* E01 = bad 'm' command */ + break; + + case 'X': /* XAA..AA,LLLL:<binary data>#cs */ + binary = 1; + case 'M': + /* MAA..AA,LLLL: Write LLLL bytes at address AA..AA - return OK */ + ptr = &inBuffer[1]; + if (hexToInt (&ptr, &addr) + && *ptr++ == ',' + && hexToInt (&ptr, &length) + && *ptr++ == ':' + && is_writeable (addr, length) ) { + if ( binary ) + hex2mem (ptr, (void *)addr, length); + else + bin2mem (ptr, (void *)addr, length); + strcpy (outBuffer, "OK"); + } + else + strcpy (outBuffer, "E02"); /* E02 = bad 'M' command */ + break; + + case 'c': + /* cAA..AA Continue at address AA..AA(optional) */ + case 's': + /* sAA..AA Step one instruction from AA..AA(optional) */ + { + /* try to read optional parameter, pc unchanged if no parm */ + ptr = &inBuffer[1]; + if (hexToInt (&ptr, &addr)) + registers[PC] = addr; + + if (inBuffer[0] == 's') + doSStep (); + } + goto stubexit; + + case 'k': /* remove all zbreaks if any */ + dumpzbreaks: + { + { + /* Unlink the entire list */ + struct z0break *z0, *znxt; + + while( (z0= z0break_list) ) + { + + /* put back the instruction */ + if( z0->instr != 0xffffffff ) + *(z0->address) = z0->instr; + + /* pop off the top entry */ + znxt = z0->next; + if( znxt ) znxt->prev = NULL; + z0break_list = znxt; + + /* and put it on the free list */ + z0->prev = NULL; + z0->next = z0break_avail; + z0break_avail = z0; + } + } + + strcpy(outBuffer, "OK"); + } + break; + + case 'q': /* queries */ +#if defined(GDB_STUB_ENABLE_THREAD_SUPPORT) + rtems_gdb_process_query( inBuffer, outBuffer, do_threads, thread ); +#endif + break; + +#if defined(GDB_STUB_ENABLE_THREAD_SUPPORT) + case 'T': + { + int testThread; + + if( vhstr2thread(&inBuffer[1], &testThread) == NULL ) + { + strcpy(outBuffer, "E01"); + break; + } + + if( rtems_gdb_index_to_stub_id(testThread) == NULL ) + { + strcpy(outBuffer, "E02"); + } + else + { + strcpy(outBuffer, "OK"); + } + } + break; +#endif + + case 'H': /* set new thread */ +#if defined(GDB_STUB_ENABLE_THREAD_SUPPORT) + if (inBuffer[1] != 'g') { + break; + } + + if (!do_threads) { + break; + } + + { + int tmp, ret; + + /* Set new generic thread */ + if (vhstr2thread(&inBuffer[2], &tmp) == NULL) { + strcpy(outBuffer, "E01"); + break; + } + + /* 0 means `thread' */ + if (tmp == 0) { + tmp = thread; + } + + if (tmp == current_thread) { + /* No changes */ + strcpy(outBuffer, "OK"); + break; + } + + /* Save current thread registers if necessary */ + if (current_thread != thread) { + ret = rtems_gdb_stub_set_thread_regs( + current_thread, + (unsigned int *) (void *)¤t_thread_registers); + ASSERT(ret); + } + + /* Read new registers if necessary */ + if (tmp != thread) { + ret = rtems_gdb_stub_get_thread_regs( + tmp, (unsigned int *) (void *)¤t_thread_registers); + + if (!ret) { + /* Thread does not exist */ + strcpy(outBuffer, "E02"); + break; + } + } + + current_thread = tmp; + strcpy(outBuffer, "OK"); + } +#endif + break; + + case 'Z': /* Add breakpoint */ + { + int ret, type, len; + unsigned char *address; + struct z0break *z0; + + ret = parse_zbreak(inBuffer, &type, &address, &len); + if (!ret) { + strcpy(outBuffer, "E01"); + break; + } + + if (type != 0) { + /* We support only software break points so far */ + strcpy(outBuffer, "E02"); + break; + } + + if (len != R_SZ) { /* was 1 */ + strcpy(outBuffer, "E03"); + break; + } + + /* Let us check whether this break point already set */ + for (z0=z0break_list; z0!=NULL; z0=z0->next) { + if (z0->address == address) { + break; + } + } + + if (z0 != NULL) { + /* we already have a breakpoint for this address */ + strcpy(outBuffer, "E04"); + break; + } + + /* Let us allocate new break point */ + if (z0break_avail == NULL) { + strcpy(outBuffer, "E05"); + break; + } + + /* Get entry */ + z0 = z0break_avail; + z0break_avail = z0break_avail->next; + + /* Let us copy memory from address add stuff the break point in */ + /* + *if (mem2hstr(z0->buf, address, 1) == NULL || + !hstr2mem(address, "cc" , 1)) { + + * Memory error * + z0->next = z0break_avail; + z0break_avail = z0; + strcpy(outBuffer, "E05"); + break; + }*/ + + /* Fill it */ + z0->address = address; + + if( z0->address == (unsigned char *) frame->epc ) + { + /* re-asserting the breakpoint that put us in here, so + we'll add the breakpoint but leave the code in place + since we'll be returning to it when the user continues */ + z0->instr = 0xffffffff; + } + else + { + /* grab the instruction */ + z0->instr = *(z0->address); + /* and insert the break */ + *(z0->address) = BREAK_INSTR; + } + + /* Add to the list */ + { + struct z0break *znxt = z0break_list; + + z0->prev = NULL; + z0->next = znxt; + + if( znxt ) znxt->prev = z0; + z0break_list = z0; + } + + strcpy(outBuffer, "OK"); + } + break; + + case 'z': /* remove breakpoint */ + if (inBuffer[1] == 'z') + { + goto dumpzbreaks; + + /* + * zz packet - remove all breaks * + z0last = NULL; + + for (z0=z0break_list; z0!=NULL; z0=z0->next) + { + if(!hstr2mem(z0->address, z0->buf, R_SZ)) + { + ret = 0; + } + z0last = z0; + } + + * Free entries if any * + if (z0last != NULL) { + z0last->next = z0break_avail; + z0break_avail = z0break_list; + z0break_list = NULL; + } + + if (ret) { + strcpy(outBuffer, "OK"); + } else { + strcpy(outBuffer, "E04"); + } + break; + */ + } + else + { + int ret, type, len; + unsigned char *address; + struct z0break *z0; + + ret = parse_zbreak(inBuffer, &type, &address, &len); + if (!ret) { + strcpy(outBuffer, "E01"); + break; + } + + if (type != 0) { + /* We support only software break points so far */ + break; + } + + if (len != R_SZ) { + strcpy(outBuffer, "E02"); + break; + } + + /* Let us check whether this break point set */ + for (z0=z0break_list; z0!=NULL; z0=z0->next) { + if (z0->address == address) { + break; + } + } + + if (z0 == NULL) { + /* Unknown breakpoint */ + strcpy(outBuffer, "E03"); + break; + } + + /* + if (!hstr2mem(z0->address, z0->buf, R_SZ)) { + strcpy(outBuffer, "E04"); + break; + }*/ + + if( z0->instr != 0xffffffff ) + { + /* put the old instruction back */ + *(z0->address) = z0->instr; + } + + /* Unlink entry */ + { + struct z0break *zprv = z0->prev, *znxt = z0->next; + + if( zprv ) zprv->next = znxt; + if( znxt ) znxt->prev = zprv; + + if( !zprv ) z0break_list = znxt; + + znxt = z0break_avail; + + z0break_avail = z0; + z0->prev = NULL; + z0->next = znxt; + } + + strcpy(outBuffer, "OK"); + } + break; + + default: /* do nothing */ + break; + } + + /* reply to the request */ + putpacket (outBuffer); + } + + stubexit: + + /* + * The original code did this in the assembly wrapper. We should consider + * doing it here before we return. + * + * On exit from the exception handler invalidate each line in the I-cache + * and write back each dirty line in the D-cache. This needs to be done + * before the target program is resumed in order to ensure that software + * breakpoints and downloaded code will actually take effect. This + * is because modifications to code in ram will affect the D-cache, + * but not necessarily the I-cache. + */ + + clear_cache(); +} + +static int numsegs; +static struct memseg memsegments[NUM_MEMSEGS]; + +int gdbstub_add_memsegment( unsigned base, unsigned end, int opts ) +{ + if( numsegs == NUM_MEMSEGS ) return -1; + + memsegments[numsegs].begin = base; + memsegments[numsegs].end = end; + memsegments[numsegs].opts = opts; + + ++numsegs; + return RTEMS_SUCCESSFUL; +} + +static int is_readable(unsigned ptr, unsigned len) +{ + struct memseg *ms; + int i; + + if( (ptr & 0x3) ) return -1; + + for(i=0; i<numsegs; i++) + { + ms= &memsegments[i]; + + if( ms->begin <= ptr && ptr+len <= ms->end && (ms->opts & MEMOPT_READABLE) ) + return -1; + } + return 0; +} + +static int is_writeable(unsigned ptr, unsigned len) +{ + struct memseg *ms; + int i; + + if( (ptr & 0x3) ) return -1; + + for(i=0; i<numsegs; i++) + { + ms= &memsegments[i]; + + if( ms->begin <= ptr && ptr+len <= ms->end && (ms->opts & MEMOPT_WRITEABLE) ) + return -1; + } + return 0; +} + +static int is_steppable(unsigned ptr) +{ + struct memseg *ms; + int i; + + if( (ptr & 0x3) ) return -1; + + for(i=0; i<numsegs; i++) + { + ms= &memsegments[i]; + + if( ms->begin <= ptr && ptr <= ms->end && (ms->opts & MEMOPT_WRITEABLE) ) + return -1; + } + return 0; +} + +static char initialized = 0; /* 0 means we are not initialized */ + +void mips_gdb_stub_install(int enableThreads) +{ + /* + These are the RTEMS-defined vectors for all the MIPS exceptions + */ + int exceptionVector[]= { MIPS_EXCEPTION_MOD, \ + MIPS_EXCEPTION_TLBL, \ + MIPS_EXCEPTION_TLBS, \ + MIPS_EXCEPTION_ADEL, \ + MIPS_EXCEPTION_ADES, \ + MIPS_EXCEPTION_IBE, \ + MIPS_EXCEPTION_DBE, \ + MIPS_EXCEPTION_SYSCALL, \ + MIPS_EXCEPTION_BREAK, \ + MIPS_EXCEPTION_RI, \ + MIPS_EXCEPTION_CPU, \ + MIPS_EXCEPTION_OVERFLOW, \ + MIPS_EXCEPTION_TRAP, \ + MIPS_EXCEPTION_VCEI, \ + MIPS_EXCEPTION_FPE, \ + MIPS_EXCEPTION_C2E, \ + MIPS_EXCEPTION_WATCH, \ + MIPS_EXCEPTION_VCED, \ + -1 }; + int i; + rtems_isr_entry old; + + if (initialized) + { + ASSERT(0); + return; + } + + memset( memsegments,0,sizeof(struct memseg)*NUM_MEMSEGS ); + numsegs = 0; + +#if defined(GDB_STUB_ENABLE_THREAD_SUPPORT) + if( enableThreads ) + do_threads = 1; + else + do_threads = 0; +#endif + + { + struct z0break *z0; + + z0break_avail = NULL; + z0break_list = NULL; + + /* z0breaks list init, now we'll do it so it makes sense... */ + for (i=0; i<BREAKNUM; i++) + { + memset( (z0= &z0break_arr[i]), 0, sizeof(struct z0break)); + + z0->next = z0break_avail; + z0break_avail = z0; + } + } + + for(i=0; exceptionVector[i] > -1; i++) + { + rtems_interrupt_catch( (rtems_isr_entry) handle_exception, exceptionVector[i], &old ); + } + + initialized = 1; + + /* get the attention of gdb */ + /* mips_break(1); disabled so user code can choose to invoke it or not */ +} |