/* -*- c++ -*- */ /* * Copyright 2009 Ettus Research LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* * Implement a eensy weensy part of the GDB Remote Serial Protocol * * See Appendix D of the GDB manual * * m, -- read bytes of memory starting at * Reply: * XX... XX... is memory contents in hex * ENN ENN NN is a hex error number * * M,:XX... -- write memory, data in hex * Reply: * OK for success * ENN for an error. NN is a hex error number * * X,:XX... -- write memory, data in binary * Reply: * OK for success * ENN for an error. NN is a hex error number * * c -- continue. is the address to resume (goto). * Reply: * * \x80 New Format... */ #include "gdbstub2.h" #include "loader_parser.h" #include "hal_uart.h" #include #include #define MAX_PACKET 1024 /* * Get raw character from serial port, no echo. */ static inline int gdb_getc(void) { return hal_uart_getc(); } /* * Put character to serial port. Raw output. */ static inline void gdb_putc(int ch) { hal_uart_putc(ch); } // ------------------------------------------------------------------------ #define GDB_ESCAPE 0x7d static unsigned char hex_table[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; static int put_hex8_checksum(int ch, int checksum) { unsigned char t = hex_table[(ch >> 4) & 0xf]; checksum += t; gdb_putc(t); t = hex_table[ch & 0xf]; checksum += t; gdb_putc(t); return checksum; } static void put_hex8(int ch) { put_hex8_checksum(ch, 0); } static bool hex4_to_bin(int ch, int *value) { if ('0' <= ch && ch <= '9'){ *value = ch - '0'; return true; } if ('a' <= ch && ch <= 'f'){ *value = ch - 'a' + 10; return true; } if ('A' <= ch && ch <= 'F'){ *value = ch - 'A' + 10; return true; } *value = 0; return false; } static bool hex8_to_bin(const unsigned char *s, int *value) { int v0, v1; if (hex4_to_bin(s[0], &v0) && hex4_to_bin(s[1], &v1)){ *value = (v0 << 4) | v1; return true; } return false; } static bool hex_to_bin_array(unsigned char *binary_data, const unsigned char *hex_data, size_t nbytes) { for (size_t i = 0; i < nbytes; i++){ int t; if (!hex8_to_bin(&hex_data[2*i], &t)) return false; binary_data[i] = t; } return true; } static bool needs_escaping(int ch) { return ch == '$' || ch == '#' || ch == GDB_ESCAPE; } /* * \brief Wait for a packet. * \param[out] pkt_buf gets the received packet payload. * \param[in] max_size is the maximum number of bytes to write into \p pkt_buf. * \param[out] actual_size is the number of bytes written to \p pkt_buf. * * \returns true iff the payload fits and the checksum is OK. * * Packets have this format: * * $# * * Where is anything and is a two byte hex * checksum. In '$', '#' and 0x7d are escaped with 0x7d. * The checksum is computed as the modulo 256 sum of all characters * btween the leading '$' and the trailing '#' (an 8-bit unsigned * checksum). */ static bool get_packet(unsigned char *pkt_buf, size_t max_size, size_t *actual_size) { typedef enum states { LOOKING_FOR_DOLLAR, LOOKING_FOR_HASH, CSUM1, CSUM2, } state_t; *actual_size = 0; unsigned char csum[2] = {0, 0}; state_t state = LOOKING_FOR_DOLLAR; size_t pi = 0; while (1){ int ch = gdb_getc(); switch (state){ case LOOKING_FOR_DOLLAR: if (ch == '$'){ pi = 0; state = LOOKING_FOR_HASH; } else if (ch == '#'){ // most likely missed the $ return false; } break; case LOOKING_FOR_HASH: if (ch == '$'){ return false; } else if (ch == '#'){ state = CSUM1; } else { if (pi >= max_size) // payload too big return false; if (ch == GDB_ESCAPE) ch = gdb_getc(); pkt_buf[pi++] = ch; } break; case CSUM1: csum[0] = ch; state = CSUM2; break; case CSUM2: csum[1] = ch; *actual_size = pi; // accept .. as a correct checksum if (csum[0] == '.' && csum[1] == '.') return true; int expected_checksum; if (!hex8_to_bin(csum, &expected_checksum)) return false; int checksum = 0; for (size_t i = 0; i < pi; i++) checksum += pkt_buf[i]; checksum &= 0xff; return checksum == expected_checksum; } } } static void put_packet_trailer(int checksum) { gdb_putc('#'); put_hex8(checksum & 0xff); gdb_putc('\r'); gdb_putc('\n'); } static void put_packet(const unsigned char *pkt_buf, size_t size) { gdb_putc('$'); int checksum = 0; for (size_t i = 0; i < size; i++){ int ch = pkt_buf[i]; if (needs_escaping(ch)) gdb_putc(GDB_ESCAPE); gdb_putc(ch); checksum += ch; } put_packet_trailer(checksum); } /*! * Read a hex number * * \param[inout] bufptr - pointer to pointer to buffer (updated on return) * \param[in] end - one past end of valid data in buf * \param[out] value - the parsed value * * \returns true iff a valid hex number was read from bufptr */ static bool parse_number(const unsigned char **bufptr, const unsigned char *end, unsigned int *value) { const unsigned char *buf = *bufptr; unsigned int v = 0; bool valid = false; int nibble; while (buf < end && hex4_to_bin(*buf, &nibble)){ valid = true; v = (v << 4) | nibble; buf++; } *value = v; *bufptr = buf; return valid; } static bool parse_char(const unsigned char **bufptr, const unsigned char *end, unsigned char *ch) { const unsigned char *buf = *bufptr; if (buf < end){ *ch = *buf++; *bufptr = buf; return true; } return false; } static bool expect_char(const unsigned char **bufptr, const unsigned char *end, unsigned char expected) { unsigned char ch; return parse_char(bufptr, end, &ch) && ch == expected; } static bool expect_end(const unsigned char **bufptr, const unsigned char *end) { return *bufptr == end; } static bool parse_addr_length(const unsigned char **bufptr, const unsigned char *end, unsigned int *addr, unsigned int *length) { return (parse_number(bufptr, end, addr) && expect_char(bufptr, end, ',') && parse_number(bufptr, end, length)); } static void put_error(int error) { unsigned char buf[3]; buf[0] = 'E'; buf[1] = hex_table[(error >> 4) & 0xf]; buf[2] = hex_table[error & 0xf]; put_packet(buf, sizeof(buf)); } static void put_ok(void) { const unsigned char buf[2] = "OK"; put_packet(buf, sizeof(buf)); } /* * Read memory and send the reply. * We do it on the fly so that our packet size is effectively unlimited */ static void read_memory(unsigned int addr, unsigned int nbytes) { int checksum = 0; gdb_putc('$'); if ((addr & 0x3) == 0 && (nbytes & 0x3) == 0){ // word aligned union { unsigned int i; unsigned char c[4]; } u; unsigned int *p = (unsigned int *) addr; unsigned int length = nbytes / 4; for (unsigned int i = 0; i < length; i++){ u.i = p[i]; // do a word read checksum = put_hex8_checksum(u.c[0], checksum); checksum = put_hex8_checksum(u.c[1], checksum); checksum = put_hex8_checksum(u.c[2], checksum); checksum = put_hex8_checksum(u.c[3], checksum); } } else { // byte aligned unsigned char *p = (unsigned char *) addr; for (unsigned int i = 0; i < nbytes; i++) checksum = put_hex8_checksum(p[i], checksum); } put_packet_trailer(checksum); } static unsigned int get_unaligned_int(const unsigned char *p) { // we're bigendian return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | (p[3]); } static bool write_memory(unsigned int addr, size_t nbytes, const unsigned char *data) { if ((addr & 0x3) == 0 && (nbytes & 0x3) == 0){ // word-aligned dst unsigned int *dst = (unsigned int *) addr; size_t length = nbytes / 4; for (size_t i = 0; i < length; i++){ unsigned int t = get_unaligned_int(&data[4*i]); dst[i] = t; // word writes } } else { // non-word-aligned dst unsigned char *dst = (unsigned char *) addr; for (size_t i = 0; i < nbytes; i++){ dst[i] = data[i]; } } return true; } void gdbstub2_main_loop(void) { unsigned char inpkt[MAX_PACKET + 24]; unsigned char binary_data[MAX_PACKET/2] __attribute__((aligned (4))); hal_uart_set_mode(UART_MODE_RAW); //tell UART HAL not to map \n to \r\n while (1){ size_t inpkt_len; bool ok = get_packet(inpkt, sizeof(inpkt), &inpkt_len); if (!ok){ gdb_putc('-'); continue; } gdb_putc('+'); const unsigned char *buf = inpkt; const unsigned char *end = inpkt + inpkt_len; unsigned char ch; if (!parse_char(&buf, end, &ch)){ // empty packet put_packet(0, 0); continue; } unsigned int addr; unsigned int length; switch(ch){ case 'm': // m, -- read bytes starting at if (!(parse_addr_length(&buf, end, &addr, &length) && expect_end(&buf, end))){ put_error(1); } else { read_memory(addr, length); } break; case 'M': // M,:XX... -- write bytes starting at // XX... is the data in hex if (!(parse_addr_length(&buf, end, &addr, &length) && expect_char(&buf, end, ':') && (end - buf) == 2 * length)){ put_error(1); } else { if (!hex_to_bin_array(binary_data, buf, length)) put_error(2); else if (!write_memory(addr, length, binary_data)) put_error(3); else put_ok(); } break; case 'X': // X,:XX... -- write bytes starting at // XX... is the data in binary if (!(parse_addr_length(&buf, end, &addr, &length) && expect_char(&buf, end, ':') && (end - buf) == length)){ put_error(1); } else { if (!write_memory(addr, length, buf)) put_error(3); else put_ok(); } break; case 'c': // c -- continue. is the address to resume (goto). if (!(parse_number(&buf, end, &addr) && expect_end(&buf, end))){ put_error(1); } else { typedef void (*fptr_t)(void); (*(fptr_t) addr)(); // most likely no return } break; /* case 0x80: { unsigned char *output = binary_data; // reuse size_t sizeof_output = sizeof(binary_data); size_t actual_olen; loader_parser(buf, end-buf, output, sizeof_output, &actual_olen); put_packet(output, actual_olen); } break; */ default: // unknown packet type put_packet(0, 0); break; } } }