/* * Copyright (c) 1995, 1996 Cygnus Support * * The authors hereby grant permission to use, copy, modify, distribute, * and license this software and its documentation for any purpose, provided * that existing copyright notices are retained in all copies and that this * notice is included verbatim in any distributions. No written agreement, * license, or royalty fee is required for any of the authorized uses. * Modifications to this software may be copyrighted by their authors * and need not follow the licensing terms described here, provided that * the new terms are clearly indicated on the first page of each file where * they apply. */ /* * A debug packet whose contents are <data> looks like: * * $ <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. * * We respond with: * * + - if CSUM is correct and ready for next packet * - - if CSUM is incorrect * * <data> is as follows: * Most values are encoded in ascii hex digits. */ #include "debug.h" #include <signal.h> /* * buffers that hold the packets while they're being constructed. */ char packet_in_buf[BUFMAX]; char packet_out_buf[BUFMAX]; int packet_index; /* * indicate to caller of mem2hex or hex2mem that there has been an error. * 0 means ok, 1 means error */ volatile int mem_err = 0; /* * 1 means print debugging messages from the target, 0 means be quiet. This is * changed by gdb_debug(). */ int remote_debug = 0; /* * indicate whether the debug vectors ahave been initialized * 0 means not yet, 1 means yep, it's ready. */ int initialized = 0; /* * These variables are instantialted in the GDB stub code. */ /* this is a list of signal to exception mappings. */ extern struct trap_info hard_trap_info[]; /* this is a memory fault exception handler, used by mem2hex & hex2mem */ extern void set_mem_fault_trap(); /* * print debugging messages. This uses print, rather than one of the * stdio routines, cause if there are stack or memory problems, the * stdio routines don't work. * params are the debug level, and the string to print * it doesn't return anything. */ void debuglog(int level, char *msg) { char *p; unsigned char buf[BUFMAX]; char newmsg[BUFMAX]; int i; if (level > remote_debug) return; if ((level <0) || (level > 100)) { print ("ERROR: debug print level out of range"); return; } /* convert some characters so it'll look right in the log */ p = newmsg; for (i = 0 ; msg[i] != '\0'; i++) { if (i > BUFMAX) print ("\r\nERROR: Debug message too long\r\n"); switch (msg[i]) { case '\n': /* newlines */ *p++ = '\\'; *p++ = 'n'; continue; case '\r': /* carriage returns */ *p++ = '\\'; *p++ = 'r'; continue; case '\033': /* escape */ *p++ = '\\'; *p++ = 'e'; continue; case '\t': /* tab */ *p++ = '\\'; *p++ = 't'; continue; case '\b': /* backspace */ *p++ = '\\'; *p++ = 'b'; continue; default: /* no change */ *p++ = msg[i]; } if (msg[i] < 26) { /* modify control characters */ *p++ = '^'; *p++ = msg[i] + 'A'; continue; } if (msg[i] >= 127) { /* modify control characters */ *p++ = '!'; *p++ = msg[i] + 'A'; continue; } } *p = '\0'; /* terminate the string */ print (newmsg); print ("\r\n"); } /* * convert an ascii hex digit to a number. * param is hex digit. * returns a decimal digit. */ int hex2digit (int digit) { if (digit == 0) return 0; if (digit >= '0' && digit <= '9') return digit - '0'; if (digit >= 'a' && digit <= 'f') return digit - 'a' + 10; if (digit >= 'A' && digit <= 'F') return digit - 'A' + 10; /* shouldn't ever get this far */ return ERROR; } /* * convert number NIB to a hex digit. * param is a decimal digit. * returns a hex digit. */ char digit2hex(int digit) { if (digit < 10) return '0' + digit; else return 'a' + digit - 10; } /* * 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. */ unsigned char * mem2hex(unsigned char *mem, unsigned char *buf, int count, int may_fault) { unsigned char ch; DEBUG (1, "In mem2hex"); set_mem_fault_trap(MAY_FAULT); while (count-- > 0) { ch = *mem++; if (mem_err) { DEBUG (1, "memory fault in mem2hex"); return 0; } *buf++ = digit2hex(ch >> 4); *buf++ = digit2hex(ch & 0xf); } *buf = 0; set_mem_fault_trap(OK); 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 */ unsigned char * hex2mem(unsigned char *buf, unsigned char *mem, int count, int may_fault) { int i; unsigned char ch; DEBUG (1, "In hex2mem"); set_mem_fault_trap(may_fault); for (i=0; i<count; i++) { ch = hex2digit(*buf++) << 4; ch |= hex2digit(*buf++); *mem++ = ch; if (mem_err) return 0; } set_mem_fault_trap(0); return mem; } /* * while we find nice hex chars, build an int. * param is a pointer to the string. * returns the int in the param field, and the number of chars processed. */ int hex2int (char **ptr, int *intValue) { int numChars = 0; int hexValue; *intValue = 0; while (**ptr) { hexValue = hex2digit(**ptr); if (hexValue < 0) break; *intValue = (*intValue << 4) | hexValue; numChars ++; (*ptr)++; } return (numChars); } /* * Scan for the sequence $<data>#<checksum> */ void getpacket(unsigned char *buffer) { unsigned char checksum; unsigned char xmitcsum; int i; int count; unsigned char ch; do { /* wait around for the start character, ignore all other characters */ while ((ch = (inbyte() & 0x7f)) != '$') ; checksum = 0; xmitcsum = -1; count = 0; /* now, read until a # or end of buffer is found */ while (count < BUFMAX) { ch = inbyte() & 0x7f; if (ch == '#') break; checksum = checksum + ch; buffer[count] = ch; count = count + 1; } if (count >= BUFMAX) continue; buffer[count] = 0; if (ch == '#') { xmitcsum = hex2digit(inbyte() & 0x7f) << 4; xmitcsum |= hex2digit(inbyte() & 0x7f); #if 1 /* Humans shouldn't have to figure out checksums to type to it. */ outbyte ('+'); return; #endif if (checksum != xmitcsum) outbyte('-'); /* failed checksum */ else { outbyte('+'); /* successful transfer */ /* if a sequence char is present, reply the sequence ID */ if (buffer[2] == ':') { outbyte(buffer[0]); outbyte(buffer[1]); /* remove sequence chars from buffer */ count = strlen(buffer); for (i=3; i <= count; i++) buffer[i-3] = buffer[i]; } } } } while (checksum != xmitcsum); } /* * Send the packet in buffer. */ void putpacket(unsigned char *buffer) { unsigned char checksum; int count; unsigned char ch; /* $<packet info>#<checksum>. */ do { outbyte('$'); checksum = 0; count = 0; while (ch = buffer[count]) { if (! outbyte(ch)) return; checksum += ch; count += 1; } outbyte('#'); outbyte(digit2hex(checksum >> 4)); outbyte(digit2hex(checksum & 0xf)); } while ((inbyte() & 0x7f) != '+'); } /* * */ void gdb_event_loop(int sigval, unsigned long *registers) { int addr; int length; unsigned char *ptr; ptr = packet_out_buf; DEBUG (1, "In gdb_event_loop"); while (1) { packet_out_buf[0] = 0; getpacket(packet_in_buf); ptr = &packet_in_buf[1]; switch (packet_in_buf[0]) { case '?': /* get the last known signal */ gdb_last_signal(sigval); break; case 'd': /* toggle debug messages from the stub */ gdb_toggle(); break; case 'g': /* return the value of the CPU registers */ target_read_registers(registers); break; case 'G': /* set the value of the CPU registers - return OK */ target_write_registers(registers); break; case 'm': /* mAA..AA,LLLL Read LLLL bytes at address AA..AA */ /* Try to read %x,%x. */ if (hex2int((char **)&ptr, &addr) && *ptr++ == ',' && hex2int((char **)&ptr, &length)) { gdb_read_memory(addr, length); } else { make_return_packet(1); } break; case 'M': /* MAA..AA,LLLL: Write LLLL bytes at address AA.AA return OK */ /* Try to read '%x,%x:'. */ if (hex2int((char **)&ptr, &addr) && *ptr++ == ',' && hex2int((char **)&ptr, &length) && *ptr++ == ':') { gdb_write_memory (addr, length, ptr); } else { make_return_packet(2); } break; case 'c': /* cAA..AA Continue at address AA..AA(optional) */ /* try to read optional parameter, pc unchanged if no parm */ if (hex2int((char **)&ptr, &addr)) { write_pc(registers, addr); } /* * we 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. */ flush_i_cache(); /* by returning, we pick up execution where we left off */ return; /* kill the program */ case 'k' : gdb_kill(); break; case 'r': /* Reset */ target_reset(); break; } /* switch */ /* reply to the request */ putpacket(packet_out_buf); } DEBUG (1, "Leaving handle_exception()"); } /* Convert the hardware trap type code to a unix signal number. */ int computeSignal(int tt) { struct trap_info *ht; for (ht = hard_trap_info; ht->tt && ht->signo; ht++) if (ht->tt == tt) return ht->signo; return SIGHUP; /* default for things we don't know about */ } /* * Set up exception handlers for tracing and breakpoints */ void set_debug_traps() { struct trap_info *ht; DEBUG (1, "Entering set_debug_traps()"); if (hard_trap_info->tt == 0) { print ("ERROR: ARG#$@%^&*!! no hard trap info!!\r\n"); } for (ht = hard_trap_info; ht->tt && ht->signo; ht++) { exception_handler(ht->tt, (unsigned long)default_trap_hook); } /* In case GDB is started before us, ack any packets (presumably "$?#xx") sitting there. */ outbyte ('+'); initialized = 1; DEBUG (1, "Leaving set_debug_traps()"); } /* * make a return packet. * param is the value to return. * 0 = OK, any other value is converted to a two digit hex number. * returns a string or "OK" or "ENN", where NN is the error number. Each N * is an ASCII encoded hex digit. */ char * make_return_packet(int val) { if (val == 0) { packet_out_buf[0] = 'O'; packet_out_buf[1] = 'K'; packet_out_buf[2] = 0; } else { packet_out_buf[0] = 'E'; packet_out_buf[1] = digit2hex((val >> 4) & 0xf); packet_out_buf[2] = digit2hex(val & 0xf); packet_out_buf[3] = 0; } return(packet_out_buf); } /* * g - read registers. * no params. * returns a vector of words, size is NUM_REGS. */ char * gdb_read_registers() { } /* * G - write registers. * param is a vector of words, size is NUM_REGS. * returns an OK or an error number. */ char * gdb_write_registers(char *regs) { } /* * m - read memory. * params are the address to start the read at and the number of * bytes to read. * returns a vector of nbytes or an error number. * Can be fewer bytes than requested if able to read only part of the * data. */ char * gdb_read_memory(long addr, int nbytes) { if (mem2hex((char *)addr, packet_out_buf, nbytes, MAY_FAULT)) return(packet_out_buf); else { return(make_return_packet(3)); } } /* * M write memory * params are the address to start writing to, the number of * bytes to write, and the new values of the bytes. * returns an OK or an error number. */ char * gdb_write_memory(long addr, int nbytes, char *mem) { if (hex2mem(mem, (char *)addr, nbytes, MAY_FAULT)) return(make_return_packet(OK)); else { return(make_return_packet(3)); } } /* * c - continue at address. * param is the address to start at, and an optional signal. If * sig is zero, then ignore it. * returns an OK or an error number. */ char * gdb_continue(int sig, long addr) { } /* * s - step instruction(s) * param is the address to start at, and an optional signal. If * sig is zero, then ignore it. * returns an OK or an error number. */ char * gdb_step(int sig, long addr) { } /* * k - kill program. * no params. * returns an OK or an error number. */ char * gdb_kill() { /* generically, we can't do anything for this command */ return(make_return_packet(OK)); } /* * ? - last signal. * no params. * returns the last signal number. */ char * gdb_last_signal(int val) { DEBUG (1, "Entering gdb_last_signal()"); packet_out_buf[0] = 'S'; packet_out_buf[1] = digit2hex(val >> 4); packet_out_buf[2] = digit2hex(val & 0xf); packet_out_buf[3] = 0; DEBUG (1, "Leaving gdb_last_signal()"); return (packet_out_buf); } /* * b - change baud rate. * param is the new baudrate * returns the baud rate. */ char * gdb_baudrate(int baud) { /* generically, we can't do anything for this command */ return(make_return_packet(OK)); } /* * T - dump state. * no params. * returns the signal number, the registers, the thread ID, and * possible extensions in a vector that looks like: * TAAn...:r...;n...:r...;n...:r...; where: * AA = signal number * n... = register number (hex) * r... = register contents * n... = `thread' * r... = thread process ID. This is a hex integer. * n... = other string not starting with valid hex digit. * gdb should ignore this n,r pair and go on to * the next. This way we can extend the protocol. */ char * gdb_dump_state() { } /* * D - host requests a detach * no params. * returns either a S, T, W, or X command. * returns an OK or an error number. */ char * gdb_detach() { } /* * H - set thread. * params are the command to execute and the thread ID. * cmd = 'c' for thread used in step and continue; * cmd = 'g' for thread used in other operations. * tid = -1 for all threads. * tid = zero, pick a thread,any thread. * returns an OK or an error number. */ char * gdb_set_thread(int cmd, int tid) { /* generically, we can't do anything for this command */ return(make_return_packet(OK)); } /* * p - read one register. * param is the register number. * returns the register value or ENN. */ char * gdb_read_reg(int reg) { /* generically, we can't do anything for this command */ return(make_return_packet(OK)); } /* * P - write one register. * params are the register number, and it's new value. * returns the register value or ENN. */ char * gdb_write_reg(int reg, long val) { /* generically, we can't do anything for this command */ return(make_return_packet(OK)); } /* * W - process exited. * no params. * returns the exit status. */ char * gdb_exited() { /* generically, we can't do anything for this command */ return(make_return_packet(OK)); } /* * X - process terminated. * no params. * returns the last signal. */ char * gdb_terminated() { } /* * O - hex encoding. * params are a vector of bytes, and the number of bytes to encode. * returns a vector of ASCII encoded hex numbers. */ char * gdb_hex(char *str, int nbytes) { } /* * A - tread alive request. * param is the thread ID. * returns an OK or an error number. */ char * gdb_thread_alive(int tid) { /* generically, we can't do anything for this command */ return(make_return_packet(OK)); } /* * ! - extended protocol. * no params. * returns an OK or an error number. */ char * gdb_extended() { /* generically, we can't do anything for this command */ return(make_return_packet(OK)); } /* * d - toggle gdb stub diagnostics. * no params. * returns an OK or an error number. */ char * gdb_debug() { if (remote_debug > 0) remote_debug = 0; else remote_debug = 1; return(make_return_packet(OK)); } /* * d - toggle gdb stub. * no params. * returns an OK or an error number. */ char * gdb_toggle() { static int level = 0; if (remote_debug) { level = remote_debug; remote_debug = 0; } else { remote_debug = level; } return(make_return_packet(OK)); } /* * r - reset target * no params. * returns an OK or an error number. */ char * gdb_reset() { /* generically, we can't do anything for this command */ return(make_return_packet(OK)); } /* * t - search backwards. * params are the address to start searching from, a pattern to match, and * the mask to use. * FIXME: not entirely sure what this is supposed to return. */ char * gdb_search(long addr, long pat, long mask) { /* generically, we can't do anything for this command */ return(make_return_packet(OK)); } /* * q - general get query. * param is a string, that's the query to be executed. * FIXME: not entirely sure what this is supposed to return. */ char * gdb_get_query(char *query) { /* generically, we can't do anything for this command */ return(make_return_packet(OK)); } /* * Q - general set query * param is a string, that's the query to be executed. * FIXME: not entirely sure what this means. * returns an OK or an error number. */ char * gdb_set(char *query) { /* generically, we can't do anything for this command */ return(make_return_packet(OK)); }