diff options
-rw-r--r-- | gdbstub.c | 108 |
1 files changed, 99 insertions, 9 deletions
@@ -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 == '}') { + /* 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 { + /* unescaped command character */ + s->line_buf[s->line_buf_index++] = ch; + s->line_sum += ch; + } + break; + case RS_GETLINE_ESC: if (ch == '#') { - s->state = RS_CHKSUM1; + /* 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 { - s->line_buf[s->line_buf_index++] = ch; + /* 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); |