gdbstub: implement remote debugging protocol escapes for command receive

- decode escape sequences
- decompress run-length encoding escape sequences
- report command parsing problems to output when debug output is enabled
- reject packet checksums that are not valid hex digits
- compute the checksum based on the packet stream, not based on the
  decoded packet

Tested with GDB and QtCreator integrated debugger on SMP QEMU instance.
Works for me.

Signed-off-by: Doug Gale <doug16k@gmail.com>
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
Doug Gale 2017-05-01 12:22:10 -04:00 committed by Stefan Hajnoczi
parent dd1559bb26
commit 4bf43122db

110
gdbstub.c
View File

@ -286,6 +286,8 @@ enum RSState {
RS_INACTIVE, RS_INACTIVE,
RS_IDLE, RS_IDLE,
RS_GETLINE, RS_GETLINE,
RS_GETLINE_ESC,
RS_GETLINE_RLE,
RS_CHKSUM1, RS_CHKSUM1,
RS_CHKSUM2, RS_CHKSUM2,
}; };
@ -296,7 +298,8 @@ typedef struct GDBState {
enum RSState state; /* parsing state */ enum RSState state; /* parsing state */
char line_buf[MAX_PACKET_LENGTH]; char line_buf[MAX_PACKET_LENGTH];
int line_buf_index; int line_buf_index;
int line_csum; int line_sum; /* running checksum */
int line_csum; /* checksum at the end of the packet */
uint8_t last_packet[MAX_PACKET_LENGTH + 4]; uint8_t last_packet[MAX_PACKET_LENGTH + 4];
int last_packet_len; int last_packet_len;
int signal; int signal;
@ -1508,7 +1511,6 @@ void gdb_do_syscall(gdb_syscall_complete_cb cb, const char *fmt, ...)
static void gdb_read_byte(GDBState *s, int ch) static void gdb_read_byte(GDBState *s, int ch)
{ {
int i, csum;
uint8_t reply; uint8_t reply;
#ifndef CONFIG_USER_ONLY #ifndef CONFIG_USER_ONLY
@ -1542,35 +1544,123 @@ static void gdb_read_byte(GDBState *s, int ch)
switch(s->state) { switch(s->state) {
case RS_IDLE: case RS_IDLE:
if (ch == '$') { if (ch == '$') {
/* start of command packet */
s->line_buf_index = 0; s->line_buf_index = 0;
s->line_sum = 0;
s->state = RS_GETLINE; s->state = RS_GETLINE;
} else {
#ifdef DEBUG_GDB
printf("gdbstub received garbage between packets: 0x%x\n", ch);
#endif
} }
break; break;
case RS_GETLINE: case RS_GETLINE:
if (ch == '#') { if (ch == '}') {
s->state = RS_CHKSUM1; /* start escape sequence */
s->state = RS_GETLINE_ESC;
s->line_sum += ch;
} else if (ch == '*') {
/* start run length encoding sequence */
s->state = RS_GETLINE_RLE;
s->line_sum += ch;
} else if (ch == '#') {
/* end of command, start of checksum*/
s->state = RS_CHKSUM1;
} else if (s->line_buf_index >= sizeof(s->line_buf) - 1) { } else if (s->line_buf_index >= sizeof(s->line_buf) - 1) {
#ifdef DEBUG_GDB
printf("gdbstub command buffer overrun, dropping command\n");
#endif
s->state = RS_IDLE; s->state = RS_IDLE;
} else { } else {
s->line_buf[s->line_buf_index++] = ch; /* unescaped command character */
s->line_buf[s->line_buf_index++] = ch;
s->line_sum += ch;
}
break;
case RS_GETLINE_ESC:
if (ch == '#') {
/* unexpected end of command in escape sequence */
s->state = RS_CHKSUM1;
} else if (s->line_buf_index >= sizeof(s->line_buf) - 1) {
/* command buffer overrun */
#ifdef DEBUG_GDB
printf("gdbstub command buffer overrun, dropping command\n");
#endif
s->state = RS_IDLE;
} else {
/* parse escaped character and leave escape state */
s->line_buf[s->line_buf_index++] = ch ^ 0x20;
s->line_sum += ch;
s->state = RS_GETLINE;
}
break;
case RS_GETLINE_RLE:
if (ch < ' ') {
/* invalid RLE count encoding */
#ifdef DEBUG_GDB
printf("gdbstub got invalid RLE count: 0x%x\n", ch);
#endif
s->state = RS_GETLINE;
} else {
/* decode repeat length */
int repeat = (unsigned char)ch - ' ' + 3;
if (s->line_buf_index + repeat >= sizeof(s->line_buf) - 1) {
/* that many repeats would overrun the command buffer */
#ifdef DEBUG_GDB
printf("gdbstub command buffer overrun,"
" dropping command\n");
#endif
s->state = RS_IDLE;
} else if (s->line_buf_index < 1) {
/* got a repeat but we have nothing to repeat */
#ifdef DEBUG_GDB
printf("gdbstub got invalid RLE sequence\n");
#endif
s->state = RS_GETLINE;
} else {
/* repeat the last character */
memset(s->line_buf + s->line_buf_index,
s->line_buf[s->line_buf_index - 1], repeat);
s->line_buf_index += repeat;
s->line_sum += ch;
s->state = RS_GETLINE;
}
} }
break; break;
case RS_CHKSUM1: case RS_CHKSUM1:
/* get high hex digit of checksum */
if (!isxdigit(ch)) {
#ifdef DEBUG_GDB
printf("gdbstub got invalid command checksum digit\n");
#endif
s->state = RS_GETLINE;
break;
}
s->line_buf[s->line_buf_index] = '\0'; s->line_buf[s->line_buf_index] = '\0';
s->line_csum = fromhex(ch) << 4; s->line_csum = fromhex(ch) << 4;
s->state = RS_CHKSUM2; s->state = RS_CHKSUM2;
break; break;
case RS_CHKSUM2: case RS_CHKSUM2:
s->line_csum |= fromhex(ch); /* get low hex digit of checksum */
csum = 0; if (!isxdigit(ch)) {
for(i = 0; i < s->line_buf_index; i++) { #ifdef DEBUG_GDB
csum += s->line_buf[i]; printf("gdbstub got invalid command checksum digit\n");
#endif
s->state = RS_GETLINE;
break;
} }
if (s->line_csum != (csum & 0xff)) { s->line_csum |= fromhex(ch);
if (s->line_csum != (s->line_sum & 0xff)) {
/* send NAK reply */
reply = '-'; reply = '-';
put_buffer(s, &reply, 1); put_buffer(s, &reply, 1);
#ifdef DEBUG_GDB
printf("gdbstub got command packet with incorrect checksum\n");
#endif
s->state = RS_IDLE; s->state = RS_IDLE;
} else { } else {
/* send ACK reply */
reply = '+'; reply = '+';
put_buffer(s, &reply, 1); put_buffer(s, &reply, 1);
s->state = gdb_handle_packet(s, s->line_buf); s->state = gdb_handle_packet(s, s->line_buf);