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
1 changed files with 100 additions and 10 deletions

110
gdbstub.c
View File

@ -286,6 +286,8 @@ enum RSState {
RS_INACTIVE,
RS_IDLE,
RS_GETLINE,
RS_GETLINE_ESC,
RS_GETLINE_RLE,
RS_CHKSUM1,
RS_CHKSUM2,
};
@ -296,7 +298,8 @@ typedef struct GDBState {
enum RSState state; /* parsing state */
char line_buf[MAX_PACKET_LENGTH];
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];
int last_packet_len;
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)
{
int i, csum;
uint8_t reply;
#ifndef CONFIG_USER_ONLY
@ -1542,35 +1544,123 @@ static void gdb_read_byte(GDBState *s, int ch)
switch(s->state) {
case RS_IDLE:
if (ch == '$') {
/* start of command packet */
s->line_buf_index = 0;
s->line_sum = 0;
s->state = RS_GETLINE;
} else {
#ifdef DEBUG_GDB
printf("gdbstub received garbage between packets: 0x%x\n", ch);
#endif
}
break;
case RS_GETLINE:
if (ch == '#') {
s->state = RS_CHKSUM1;
if (ch == '}') {
/* 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) {
#ifdef DEBUG_GDB
printf("gdbstub command buffer overrun, dropping command\n");
#endif
s->state = RS_IDLE;
} 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;
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_csum = fromhex(ch) << 4;
s->state = RS_CHKSUM2;
break;
case RS_CHKSUM2:
s->line_csum |= fromhex(ch);
csum = 0;
for(i = 0; i < s->line_buf_index; i++) {
csum += s->line_buf[i];
/* get low hex digit of checksum */
if (!isxdigit(ch)) {
#ifdef DEBUG_GDB
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 = '-';
put_buffer(s, &reply, 1);
#ifdef DEBUG_GDB
printf("gdbstub got command packet with incorrect checksum\n");
#endif
s->state = RS_IDLE;
} else {
/* send ACK reply */
reply = '+';
put_buffer(s, &reply, 1);
s->state = gdb_handle_packet(s, s->line_buf);