/* * $QNXLicenseC: * Copyright 2007, QNX Software Systems. All Rights Reserved. * * You must obtain a written license from and pay applicable license fees to QNX * Software Systems before you may reproduce, modify or distribute this software, * or any work that includes all or part of this software. Free development * licenses are available for evaluation and non-commercial purposes. For more * information visit http://licensing.qnx.com or email licensing@qnx.com. * * This file may contain contributions from others. Please review this entire * file for other proprietary rights or license notices, as well as the QNX * Development Suite License Guide at http://licensing.qnx.com/license-guide/ * for other information. * $ */ /* * * The following gdb commands are supported: * * command function Return value * * g return the value of the CPU registers hex data or ENN * * mAA..AA,LLLL Read LLLL bytes at address AA..AA hex data or ENN * * k kill * D detach * H[c,g]tid set the current thread OK * ? return the signal number Kxx * qC return the current thread id * qRcmd,kprintf * return the kprintf ring buffer O * qRcmd,pid [-] * switch pgtbl,regset to given pid,tid O * q[f,s]ThreadInfo * return list of thread id's m or l * * All commands and responses are sent with a packet which includes a * checksum. A packet consists of * * $#. * * where * :: * :: < two hex digits computed as modulo 256 sum of > * * 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 #include "kdserver.h" static char *inbuf; static char *outbuf; static char *scratch; static unsigned buff_max; static struct kdump *kdp; static paddr_t current_prp; static unsigned current_tid = 1; static void (*gdb_expand)(char *src, char *dest); static unsigned (*gdb_compress)(char *src, char *dest); static int dbg_getc(void) { unsigned char ch; read(0, &ch, 1); return ch; } static void dbg_write(const void *p, unsigned len) { write(1, p, len); } /* * chartohex * Convert char to hex nibble */ static int chartohex(unsigned char ch) { if((ch >= 'A') && (ch <= 'F')) return (ch-'A'+10); if((ch >= 'a') && (ch <= 'f')) return (ch-'a'+10); if((ch >= '0') && (ch <= '9')) return (ch-'0'); return 0; } static char tohexchar(unsigned char c) { static unsigned char const hexchars[] = "0123456789abcdef"; return hexchars[c & 0xf]; } static unsigned cisco_gdb_compress(char *src, char *dest) { char *start = dest; char previous = 0; int repeat = 0; do { if((*src == previous) && (repeat != 255)) { repeat++; } else { if(repeat > 3) { dest = dest-repeat; *dest++ = '*'; *dest++ = tohexchar(repeat >> 4); *dest++ = tohexchar(repeat); } repeat = 0; } *dest++ = *src; previous = *src; } while(*src++); return dest - start; } static void cisco_gdb_expand(char *src, char *dest) { int i; int repeat; do { if(*src == '*') { repeat = (chartohex(src[1]) << 4) + chartohex(src[2]); for (i = 0; i < repeat; i++) { *dest++ = *(src-1); } src += 2; } else { *dest++ = *src; } } while(*src++); } static unsigned generic_gdb_compress(char *src, char *dest) { char *start = dest; char previous = 0; int repeat = 0; unsigned min_repeat = 3; do { if((*src == previous) && (repeat < 97)) { repeat++; } else { if(repeat > min_repeat) { dest -= repeat; *dest++ = '*'; *dest++ = repeat + 29; /* make sure four '|' characters never come out in a row */ min_repeat = (repeat == ('|' - 29)) ? 2 : 3; } repeat = 0; } *dest++ = *src; previous = *src; } while(*src++); return dest - start; } static void generic_gdb_expand(char *src, char *dest) { int i; int repeat; do { if(*src == '*') { repeat = src[1] - 29; for(i = 0; i < repeat; i++) { *dest++ = src[-1]; } src += 2; } else { *dest++ = *src; } } while(*src++); } /* * gethexnum - parse a hex string returning a value * * This is a low-level routine for converting hex values into binary. It * is used by the 'parse' routines to actually perform the conversion. * * Entry : srcstr - Pointer to string to convert * retstr - Pointer to cell which will contain the address * of the first byte not converted * - Pointer to cell to return value */ static int gethexnum(char *srcstr, char **retstr, unsigned *retvalue) { char *str = srcstr; unsigned long value = 0; /* Convert all of the digits until we get a non-hex digit */ while(*str && (((*str >= 'a') && (*str <= 'f')) || ((*str >= '0') && (*str <= '9')))) { value = value*16 + (*str <= '9' ? (*str++ -'0') : (*str++ -'a'+10)); } /* Return failure if we are still pointing at the start */ if(str == srcstr) return 0; /* Set up the return values and return success */ *retvalue = value; *retstr = str; return 1; } /* * parse2hexnum - Parse two hex numbers * * This routine converts a string of two numbers, seperated by commas, * into two binary values. Note that if either of the values can not * be returned, this routine will return failure and not update either * return value. */ static int parse2hexnum(char *srcstr, unsigned *retvalue1, unsigned *retvalue2) { char *str; unsigned value1, value2; if(!gethexnum(srcstr, &str, &value1) || (*str++ != ',') || !gethexnum(str, &str, &value2)) { return 0; } *retvalue1 = value1; *retvalue2 = value2; return 1; } /* * scan for the sequence $# */ static int getpacket() { unsigned char checksum; unsigned char xmitcsum; int i; int count; int ch; int cs1; int cs2; for( ;; ) { try_again: /* wait around for the start character, ignore all other characters */ do { ch = dbg_getc(); if(ch == -1) return 0; } while(ch != '$'); try_again2: checksum = 0; count = 0; cs1 = cs2 = 0; /* now, read until a # or end of buffer is found */ for( ;; ) { if(count >= buff_max) goto try_again; ch = dbg_getc(); if(ch == -1) return 0; if(ch == '#') break; if(ch == '$') goto try_again2; checksum = checksum + ch; scratch[count++] = ch; } /* collect the checksum */ cs1 = dbg_getc(); if(cs1 == -1) return 0; cs2 = dbg_getc(); if(cs2 == -1) return 0; scratch[count] = 0; gdb_expand(scratch, inbuf); xmitcsum = (chartohex(cs1) << 4) + chartohex(cs2); if(checksum == xmitcsum) break; if(debug_flag) { fprintf(stderr, "bad checksum. My count = 0x%x, sent=0x%x. buf=%s\n", checksum,xmitcsum,inbuf); } dbg_write("-", 1); /* failed checksum */ } dbg_write("+", 1); /* successful transfer */ /* if a sequence char is present, reply the sequence ID */ if(inbuf[2] == ':') { dbg_write(&inbuf[0], 2); /* remove sequence chars from buffer */ i = 3; for( ;; ) { inbuf[i-3] = inbuf[i]; if(inbuf[i] == '\0') break; ++i; } } return 1; } /* * send the packet in buffer. The host gets one chance to read it. * This routine does not wait for a positive acknowledge. */ static void putpacket(void) { unsigned char checksum; unsigned len; unsigned i; len = gdb_compress(outbuf, &scratch[1]); /* $#. */ checksum = 0; for(i = 1; i <= len; ++i) { checksum += scratch[i]; } scratch[0] = '$'; scratch[len + 1] = '#'; scratch[len + 2] = tohexchar(checksum >> 4); scratch[len + 3] = tohexchar(checksum >> 0); dbg_write(scratch, len + 4); } /* * convert the memory pointed to by mem into hex, placing result in buf * return a pointer to the last char put in buf (null) */ static char * mem2hex(char *mem, char *buf, int count) { int i; unsigned char ch; for(i=0;i> 4); *buf++ = tohexchar(ch); } *buf = 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 */ char * hex2mem(char *buf, char *mem, int count) { int i; unsigned char ch; for(i=0;i 0) { fprintf(stderr, "no memory for kdump info\n"); } return; } // We allocated one byte more than we really want // so we can ensure there's always a trailing '\0' core_read_paddr(kdump_paddr, kdp, len - 1); kdp->kp_buff[len-1] = '\0'; } p = &kdp->kp_buff[0]; while(*p != '\0') { len = strlen(p); if(len > 100) len = 100; outbuf[0] = 'O'; mem2hex(p, &outbuf[1], len); putpacket(); p += len; } strcpy(outbuf, "OK"); } static void monitor_pid(char *p) { unsigned pid; unsigned tid; paddr64_t pid_paddr; paddr64_t tid_paddr; unsigned len; while(isspace(*p)) ++p; pid = strtoul(p, &p, 0); if(pid == 0) { set_default_dump_state(); current_prp = 0; current_tid = 1; #define ORIG_MSG "Returned to original dump state\n" mem2hex(ORIG_MSG, outbuf, sizeof(ORIG_MSG)-1); return; } while(isspace(*p)) ++p; if(*p == '-') { ++p; while(isspace(*p)) ++p; tid = strtoul(p, &p, 0); } else { tid = 0; } pid_paddr = find_pid(pid); if(pid_paddr == 0) { #define PID_MSG "No such process\n" mem2hex(PID_MSG, outbuf, sizeof(PID_MSG)-1); return; } tid_paddr = find_tid(pid_paddr, &tid); if(tid_paddr == 0) { #define TID_MSG "No such thread\n" mem2hex(TID_MSG, outbuf, sizeof(TID_MSG)-1); return; } current_prp = pid_paddr; current_tid = tid; set_pgtbl(pid_paddr); set_regset(tid_paddr); len = sprintf(scratch, "switched to pid %d, thread %d\n", pid, tid); mem2hex(scratch, outbuf, len); } /* * This function does all command procesing for interfacing to gdb. */ void server() { char *p; unsigned tid; paddr64_t tid_paddr; if(protocol == 0) { /* generic GDB protocol compression */ gdb_expand = generic_gdb_expand; gdb_compress = generic_gdb_compress; } else { /* cisco GDB protocol compression */ gdb_expand = cisco_gdb_expand; gdb_compress = cisco_gdb_compress; } /* * Define the size of the buffers used for communications with the remote * GDB. This value must match the value used by GDB, or the protocol will * break. */ buff_max = max(400, cpu->gdb_regset_size*2 + 32); inbuf = malloc(buff_max); outbuf = malloc(buff_max); scratch = malloc(buff_max + 10); if((inbuf == NULL) || (outbuf == NULL) || (scratch == NULL)) { fprintf(stderr, "No memory for buffers\n"); return; } while(getpacket()) { outbuf[0] = 0; switch(inbuf[0]) { case 'c': // continue case 'C': // continue with signal case 's': // step case 'S': // step with signal case '?': // Tell GDB our signal number sprintf(outbuf, "T%02xthread:%X;", note->sig_num, current_tid); break; case 'g' : // return the value of the CPU registers cpu->cvt_regset(regset, scratch); mem2hex(scratch, outbuf, cpu->gdb_regset_size); break; case 'm' : // mAA..AA,LLLL Read LLLL bytes at address AA..AA gdb_read_membytes(); break; case 'k' : // kill program case 'D' : // detach program putpacket(); /*ACK the packet early (since we're going bye-bye) */ return; case 'H': // set active thread if((inbuf[1] == 'g') && (inbuf[1] != '-')) { gethexnum(&inbuf[2], &p, &tid); if(current_prp == 0) { if((tid != 0) && (tid != 1)) { // bad tid strcpy(outbuf, "E03"); break; } } else { tid_paddr = find_tid(current_prp, &tid); if(tid_paddr == 0) { // bad tid strcpy(outbuf, "E03"); break; } set_regset(tid_paddr); current_tid = tid; } } strcpy(outbuf, "OK"); break; case 'T': // test if thread is alive gethexnum(&inbuf[1], &p, &tid); if(current_prp == 0) { if((tid != 0) && (tid != 1)) { // bad tid strcpy(outbuf, "E03"); break; } } else { unsigned check_tid = tid; tid_paddr = find_tid(current_prp, &check_tid); if((tid_paddr == 0) || (check_tid != tid)) { // bad tid strcpy(outbuf, "E03"); break; } } strcpy(outbuf, "OK"); break; case 'q': // generic query if(memcmp(&inbuf[1], "C", 2) == 0) { // return current thread ID sprintf(outbuf, "%X", current_tid); } else if(memcmp(&inbuf[2], "ThreadInfo", 11) == 0) { // return thread list static unsigned next_tid; paddr64_t tid_paddr; unsigned tid; if(inbuf[1] == 'f') { // first time, reset the index next_tid = 1; } tid = next_tid; if(current_prp != 0) { tid_paddr = find_tid(current_prp, &tid); } else if(tid <= 1) { tid_paddr = 1; } else { tid_paddr = 0; } if(tid_paddr == 0) { strcpy(outbuf, "l"); } else { sprintf(outbuf, "m%X", tid); next_tid = tid + 1; } } else if(memcmp(&inbuf[1], "Rcmd,", 5) == 0) { // remote command p = &inbuf[6]; hex2mem(p, scratch, strlen(p)); if(strcmp(scratch, "kprintf") == 0) { // We're going to use the "monitor kprintf" command // to return the information from the kprintf ring buffer // log. monitor_kprintf(); } else if(memcmp(scratch, "pid ", 4) == 0) { // The "monitor pid [-]" command is used to switch // the the page table & register set to the given monitor_pid(&scratch[4]); } } break; } /* switch */ /* reply to the request */ putpacket(); } }