diff --git a/Makefile b/Makefile index 13d27a1..b50a0f9 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ install: mspdebug mspdebug.man .SUFFIXES: .c .o mspdebug: main.o fet.o rf2500.o dis.o uif.o ihex.o elf32.o stab.o util.o \ - bsl.o sim.o symmap.o + bsl.o sim.o symmap.o gdb.o $(CC) $(CFLAGS) -o $@ $^ -lusb .c.o: diff --git a/bsl.c b/bsl.c index ae75348..ed14157 100644 --- a/bsl.c +++ b/bsl.c @@ -202,7 +202,7 @@ static int bsl_control(device_ctl_t type) return -1; } -static int bsl_wait(void) +static int bsl_wait(int blocking) { return 0; } diff --git a/device.h b/device.h index 11cca94..c7e84b4 100644 --- a/device.h +++ b/device.h @@ -36,7 +36,7 @@ typedef enum { struct device { void (*close)(void); int (*control)(device_ctl_t action); - int (*wait)(void); + int (*wait)(int blocking); int (*breakpoint)(u_int16_t addr); int (*getregs)(u_int16_t *regs); int (*setregs)(const u_int16_t *regs); diff --git a/fet.c b/fet.c index 60a5929..4b3415c 100644 --- a/fet.c +++ b/fet.c @@ -602,9 +602,9 @@ static int do_erase(void) return 0; } -static int fet_wait(void) +static int fet_wait(int blocking) { - for (;;) { + do { /* Without this delay, breakpoints can get lost. */ if (usleep(500000) < 0) break; @@ -616,7 +616,7 @@ static int fet_wait(void) if (!(fet_reply.argv[0] & FET_POLL_RUNNING)) return 0; - } + } while (blocking); return 1; } @@ -639,7 +639,7 @@ static int fet_control(device_ctl_t action) case DEVICE_CTL_STEP: if (do_run(FET_RUN_STEP) < 0) return -1; - if (fet_wait() < 0) + if (fet_wait(1) < 0) return -1; return 0; diff --git a/gdb.c b/gdb.c new file mode 100644 index 0000000..8591332 --- /dev/null +++ b/gdb.c @@ -0,0 +1,575 @@ +/* MSPDebug - debugging tool for MSP430 MCUs + * Copyright (C) 2009, 2010 Daniel Beer + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "device.h" +#include "gdb.h" + +static const struct device *gdb_device; + +/************************************************************************ + * GDB IO routines + */ + +static int gdb_socket; +static int gdb_errno; + +static char gdb_xbuf[1024]; +static int gdb_head; +static int gdb_tail; + +static char gdb_outbuf[1024]; +static int gdb_outlen; + +static void gdb_printf(const char *fmt, ...) +{ + va_list ap; + int len; + + va_start(ap, fmt); + len = vsnprintf(gdb_outbuf + gdb_outlen, + sizeof(gdb_outbuf) - gdb_outlen, + fmt, ap); + va_end(ap); + + gdb_outlen += len; +} + +static int gdb_flush(void) +{ + if (send(gdb_socket, gdb_outbuf, gdb_outlen, 0) < 0) { + gdb_errno = errno; + perror("gdb: send"); + return -1; + } + + gdb_outlen = 0; + return 0; +} + +static int gdb_read(int blocking) +{ + fd_set r; + int len; + struct timeval to = { + .tv_sec = 0, + .tv_usec = 0 + }; + + FD_ZERO(&r); + FD_SET(gdb_socket, &r); + + if (select(gdb_socket + 1, &r, NULL, NULL, + blocking ? NULL : &to) < 0) { + perror("gdb: select"); + return -1; + } + + if (!FD_ISSET(gdb_socket, &r)) + return 0; + + len = recv(gdb_socket, gdb_xbuf, sizeof(gdb_xbuf), 0); + + if (len < 0) { + gdb_errno = errno; + perror("gdb: recv"); + return -1; + } + + if (!len) { + printf("Connection closed\n"); + return -1; + } + + gdb_head = 0; + gdb_tail = len; + return len; +} + +static int gdb_peek(void) +{ + if (gdb_head == gdb_tail && gdb_read(0) < 0) + return -1; + + return gdb_head != gdb_tail; +} + +static int gdb_getc(void) +{ + int c; + + /* If the buffer is empty, receive some more data */ + if (gdb_head == gdb_tail && gdb_read(1) < 0) + return -1; + + c = gdb_xbuf[gdb_head]; + gdb_head++; + + return c; +} + +static int gdb_flush_ack(void) +{ + int c; + + do { + gdb_outbuf[gdb_outlen] = 0; +#ifdef DEBUG_GDB + printf("-> %s\n", gdb_outbuf); +#endif + if (gdb_flush() < 0) + return -1; + + c = gdb_getc(); + if (c < 0) + return -1; + } while (c != '+'); + + return 0; +} + +static void gdb_packet_start(void) +{ + gdb_printf("$"); +} + +static void gdb_packet_end(void) +{ + int i; + int c = 0; + + for (i = 1; i < gdb_outlen; i++) + c = (c + gdb_outbuf[i]) & 0xff; + gdb_printf("#%02X", c); +} + +static void gdb_hexstring(const char *text) +{ + while (*text) + gdb_printf("%02X", *(text++)); +} + +static int hexval(int c) +{ + if (isdigit(c)) + return c - '0'; + if (isupper(c)) + return c - 'A' + 10; + if (islower(c)) + return c - 'a' + 10; + + return 0; +} + +/************************************************************************ + * GDB server + */ + +static void read_registers(void) +{ + u_int16_t regs[DEVICE_NUM_REGS]; + + printf("Reading registers\n"); + if (gdb_device->getregs(regs) < 0) { + gdb_printf("E00"); + } else { + int i; + + for (i = 0; i < DEVICE_NUM_REGS; i++) + gdb_printf("%02X%02X", regs[i] & 0xff, regs[i] >> 8); + } +} + +static void monitor_command(char *buf) +{ + char cmd[128]; + int len = 0; + + while (len + 1 < sizeof(cmd) && *buf && buf[1]) { + cmd[len++] = (hexval(buf[0]) << 4) | hexval(buf[1]); + buf += 2; + } + + if (!strcasecmp(cmd, "reset")) { + printf("Resetting device\n"); + if (gdb_device->control(DEVICE_CTL_RESET) < 0) + gdb_hexstring("Reset failed\n"); + else + gdb_printf("OK"); + } else if (!strcasecmp(cmd, "erase")) { + printf("Erasing device\n"); + if (gdb_device->control(DEVICE_CTL_ERASE) < 0) + gdb_hexstring("Erase failed\n"); + else + gdb_printf("OK"); + } +} + +static void write_registers(char *buf) +{ + u_int16_t regs[DEVICE_NUM_REGS]; + int i; + + printf("Writing registers\n"); + for (i = 0; i < DEVICE_NUM_REGS; i++) { + regs[i] = (hexval(buf[2]) << 12) | + (hexval(buf[3]) << 8) | + (hexval(buf[0]) << 4) | + hexval(buf[1]); + buf += 4; + } + + if (gdb_device->setregs(regs) < 0) + gdb_printf("E00"); + else + gdb_printf("OK"); +} + +static void read_memory(char *text) +{ + char *length_text = strchr(text, ','); + int length, addr; + u_int8_t buf[128]; + + if (!length_text) { + fprintf(stderr, "gdb: malformed memory read request\n"); + gdb_printf("E00"); + return; + } + + *(length_text++) = 0; + + length = strtoul(length_text, NULL, 16); + addr = strtoul(text, NULL, 16); + + if (length > sizeof(buf)) + length = sizeof(buf); + + printf("Reading %d bytes from 0x%04x\n", length, addr); + + if (gdb_device->readmem(addr, buf, length) < 0) { + gdb_printf("E00"); + } else { + int i; + + for (i = 0; i < length; i++) + gdb_printf("%02X", buf[i]); + } +} + +static void write_memory(char *text) +{ + char *data_text = strchr(text, ':'); + char *length_text = strchr(text, ','); + int length, addr; + u_int8_t buf[128]; + int buflen = 0; + + if (!(data_text && length_text)) { + fprintf(stderr, "gdb: malformed memory write request\n"); + gdb_printf("E00"); + return; + } + + *(data_text++) = 0; + *(length_text++) = 0; + + length = strtoul(length_text, NULL, 16); + addr = strtoul(text, NULL, 16); + + while (buflen < sizeof(buf) && *data_text && data_text[1]) { + buf[buflen++] = (hexval(data_text[0]) << 4) | + hexval(data_text[1]); + data_text += 2; + } + + if (buflen != length) { + fprintf(stderr, "gdb: length mismatch\n"); + gdb_printf("E00"); + return; + } + + printf("Writing %d bytes to 0x%04x\n", buflen, addr); + + if (gdb_device->writemem(addr, buf, buflen) < 0) + gdb_printf("E00"); + else + gdb_printf("OK"); +} + +static void single_step(char *buf) +{ + u_int16_t regs[DEVICE_NUM_REGS]; + int i; + + printf("Single stepping\n"); + + if (*buf) { + if (gdb_device->getregs(regs) < 0) + goto fail; + + regs[0] = strtoul(buf, NULL, 16); + if (gdb_device->setregs(regs) < 0) + goto fail; + } + + if (gdb_device->control(DEVICE_CTL_STEP) < 0) + goto fail; + if (gdb_device->getregs(regs) < 0) + goto fail; + + gdb_printf("T00"); + for (i = 0; i < 16; i++) + gdb_printf("%02X:%02X%02X;", i, regs[i] & 0xff, regs[i] >> 8); + + return; + fail: + gdb_printf("E00"); +} + +static void run(char *buf) +{ + u_int16_t regs[DEVICE_NUM_REGS]; + int i; + + printf("Running\n"); + + if (*buf) { + if (gdb_device->getregs(regs) < 0) + goto fail; + + regs[0] = strtoul(buf, NULL, 16); + if (gdb_device->setregs(regs) < 0) + goto fail; + } + + if (gdb_device->control(DEVICE_CTL_RUN) < 0) + goto fail; + + for (;;) { + int status = gdb_device->wait(0); + + if (status < 0) + goto fail; + if (!status) { + printf("Target halted\n"); + goto out; + } + + while (gdb_peek()) { + int c = gdb_getc(); + + if (c < 0) + return; + + if (c == 3) { + printf("Interrupted by gdb\n"); + goto out; + } + } + } + + out: + if (gdb_device->control(DEVICE_CTL_HALT) < 0) + goto fail; + if (gdb_device->getregs(regs) < 0) + goto fail; + + gdb_printf("T00"); + for (i = 0; i < 16; i++) + gdb_printf("%02X:%02X%02X;", i, regs[i] & 0xff, regs[i] >> 8); + + return; + fail: + gdb_printf("E00"); +} + +static int process_command(char *buf, int len) +{ + gdb_packet_start(); + + switch (buf[0]) { + case '?': /* Return target halt reason */ + gdb_printf("T00"); + break; + + case 'g': /* Read registers */ + read_registers(); + break; + + case 'G': /* Write registers */ + if (len >= DEVICE_NUM_REGS * 4) + write_registers(buf + 1); + else + gdb_printf("E00"); + break; + + case 'q': /* Query */ + if (!strncmp(buf, "qRcmd,", 6)) + monitor_command(buf + 6); + break; + + case 'm': /* Read memory */ + read_memory(buf + 1); + break; + + case 'M': /* Write memory */ + write_memory(buf + 1); + break; + + case 'c': /* Continue */ + run(buf + 1); + break; + + case 's': /* Single step */ + single_step(buf + 1); + break; + } + + /* For unknown/unsupported packets, return an empty reply */ + gdb_packet_end(); + return gdb_flush_ack(); +} + +static void reader_loop(void) +{ + for (;;) { + char buf[1024]; + int len = 0; + int cksum_calc = 0; + int cksum_recv = 0; + int c; + + /* Wait for packet start */ + do { + c = gdb_getc(); + if (c < 0) + return; + } while (c != '$'); + + /* Read packet payload */ + while (len + 1 < sizeof(buf)) { + c = gdb_getc(); + if (c < 0) + return; + if (c == '#') + break; + + buf[len++] = c; + cksum_calc = (cksum_calc + c) & 0xff; + } + buf[len] = 0; + + /* Read packet checksum */ + c = gdb_getc(); + if (c < 0) + return; + cksum_recv = hexval(c); + c = gdb_getc(); + if (c < 0) + return; + cksum_recv = (cksum_recv << 4) | hexval(c); + +#ifdef DEBUG_GDB + printf("<- $%s#%02X\n", buf, cksum_recv); +#endif + + if (cksum_recv != cksum_calc) { + fprintf(stderr, "gdb: bad checksum (calc = 0x%02x, " + "recv = 0x%02x)\n", cksum_calc, cksum_recv); + fprintf(stderr, "gdb: packet data was: %s\n", buf); + gdb_printf("-"); + if (gdb_flush() < 0) + return; + continue; + } + + /* Send acknowledgement */ + gdb_printf("+"); + if (gdb_flush() < 0) + return; + + if (len && process_command(buf, len) < 0) + return; + } +} + +int gdb_server(const struct device *dev, int port) +{ + int sock; + int client; + struct sockaddr_in addr; + socklen_t len; + + sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock < 0) { + perror("gdb: can't create socket"); + return -1; + } + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_ANY); + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + fprintf(stderr, "gdb: can't bind to port %d: %s\n", + port, strerror(errno)); + close(sock); + return -1; + } + + if (listen(sock, 1) < 0) { + perror("gdb: can't listen on socket"); + close(sock); + return -1; + } + + printf("Bound to port %d. Now waiting for connection...\n", port); + + len = sizeof(addr); + client = accept(sock, (struct sockaddr *)&addr, &len); + if (client < 0) { + perror("gdb: failed to accept connection"); + close(sock); + return -1; + } + + close(sock); + printf("Client connected from %s:%d\n", + inet_ntoa(addr.sin_addr), htons(addr.sin_port)); + + gdb_socket = client; + gdb_errno = 0; + gdb_head = 0; + gdb_tail = 0; + gdb_outlen = 0; + gdb_device = dev; + + reader_loop(); + + return gdb_errno ? -1 : 0; +} diff --git a/gdb.h b/gdb.h new file mode 100644 index 0000000..9f1fac8 --- /dev/null +++ b/gdb.h @@ -0,0 +1,29 @@ +/* MSPDebug - debugging tool for MSP430 MCUs + * Copyright (C) 2009, 2010 Daniel Beer + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef GDB_H_ +#define GDB_H_ + +/* Start a GDB remote stub, listening on the given TCP/IP port. It will + * wait for GDB to connect, and then return once GDB has disconnected. + * + * Returns 0 for success, or -1 in the case of an error. + */ +int gdb_server(const struct device *dev, int port); + +#endif diff --git a/main.c b/main.c index 49f73b7..70fe4bf 100644 --- a/main.c +++ b/main.c @@ -28,6 +28,7 @@ #include "binfile.h" #include "stab.h" #include "util.h" +#include "gdb.h" static const struct device *msp430_dev; @@ -404,11 +405,9 @@ static int cmd_run(char **arg) printf("Running to 0x%04x.", bp_addr); else printf("Running."); - printf(" Press Ctrl+C to interrupt..."); - fflush(stdout); + printf(" Press Ctrl+C to interrupt...\n"); - msp430_dev->wait(); - printf("\n"); + msp430_dev->wait(1); if (msp430_dev->control(DEVICE_CTL_HALT) < 0) return -1; @@ -631,6 +630,22 @@ static int cmd_syms(char **arg) return result; } +static int cmd_gdb(char **arg) +{ + char *port_text = get_arg(arg); + int port = 2000; + + if (port_text) + port = atoi(port_text); + + if (port <= 0 || port > 65535) { + fprintf(stderr, "gdb: invalid port: %d\n", port); + return -1; + } + + return gdb_server(msp430_dev, port); +} + static const struct command all_commands[] = { {"=", cmd_eval, "= \n" @@ -638,6 +653,9 @@ static const struct command all_commands[] = { {"dis", cmd_dis, "dis
[length]\n" " Disassemble a section of memory.\n"}, + {"gdb", cmd_gdb, +"gdb [port]\n" +" Run a GDB remote stub on the given TCP/IP port.\n"}, {"help", cmd_help, "help [command]\n" " Without arguments, displays a list of commands. With a command name as\n" @@ -764,7 +782,6 @@ static void reader_loop(void) { printf("\n"); cmd_help(NULL); - ctrlc_init(); for (;;) { char buf[128]; @@ -892,6 +909,8 @@ int main(int argc, char **argv) return -1; } + ctrlc_init(); + /* Open a device */ if (mode == MODE_SIM) { msp430_dev = sim_open(); diff --git a/mspdebug.man b/mspdebug.man index 19d5976..de11bc9 100644 --- a/mspdebug.man +++ b/mspdebug.man @@ -22,7 +22,8 @@ registers, set registers, and control the CPU (single step, run and run to breakpoint). It supports ELF32, Intel HEX and BSD-style symbol tables (such as the -output produced by \fBnm\fR(1)). +output produced by \fBnm\fR(1)). It can also be used as a remote stub +for \fBgdb\fR(1). .SH OPTIONS Command-line options accepted by MSPDebug are described below. If commands are specified on the end of the command-line, then they are @@ -93,6 +94,13 @@ length (64 bytes) is disassembled and shown. If symbols are available, then all addresses used as operands are translated into \fIsymbol\fR+\fIoffset\fR form. +.IP "gdb [\fIport\fR]" +Start a GDB remote stub, optionally specifying a TCP port to listen on. +If no port is given, the default port is 2000. + +MSPDebug will wait for a connection on this port, and then act as a +GDB remote stub until GDB disconnects. The basic GDB protocol is +supported, plus the monitor commands "erase" and "reset". .IP "help [\fIcommand\fR]" Show a brief listing of available commands. If an argument is specified, show the syntax for the given command. The help text shown @@ -183,8 +191,7 @@ The following are all valid examples of address expressions: .br .B __bss_end-__bss_start .SH SEE ALSO -.BR nm (1), -.BR objcopy (1) +\fBnm\fR(1), \fBgdb\fR(1), \fBobjcopy\fR(1) .SH BUGS If you find any bugs, you should report them to the author at daniel@tortek.co.nz. It would help if you could include a transcript diff --git a/sim.c b/sim.c index 897882b..f2e3240 100644 --- a/sim.c +++ b/sim.c @@ -78,8 +78,10 @@ static u_int16_t fetch_io(u_int16_t addr, int is_byte) printf("? "); fflush(stdout); - if (!fgets(text, sizeof(text), stdin)) + if (!fgets(text, sizeof(text), stdin)) { + printf("\n"); return 0; + } len = strlen(text); while (len && isspace(text[len - 1])) @@ -479,13 +481,14 @@ static int sim_control(device_ctl_t action) case DEVICE_CTL_RESET: memset(sim_regs, 0, sizeof(sim_regs)); sim_regs[MSP430_REG_PC] = MEM_GETW(0xfffe); - break; + return 0; case DEVICE_CTL_ERASE: memset(memory, 0xff, MEM_SIZE); return 0; case DEVICE_CTL_HALT: + run_mode = RUN_HALTED; return 0; case DEVICE_CTL_STEP: @@ -503,34 +506,39 @@ static int sim_control(device_ctl_t action) return -1; } -static int sim_wait(void) +static int sim_wait(int blocking) { + int i = 100000; + if (run_mode != RUN_HALTED) { - printf("\n"); ctrlc_reset(); - for (;;) { + while (i) { if (run_mode == RUN_TO_BREAKPOINT && - sim_regs[MSP430_REG_PC] == run_breakpoint) - break; + sim_regs[MSP430_REG_PC] == run_breakpoint) { + run_mode = RUN_HALTED; + return 0; + } if (sim_regs[MSP430_REG_SR] & MSP430_SR_CPUOFF) { + run_mode = RUN_HALTED; printf("CPU disabled\n"); - break; + return 0; } - if (ctrlc_check()) { - run_mode = RUN_HALTED; - return 1; - } + if (ctrlc_check()) + break; if (step_cpu() < 0) { run_mode = RUN_HALTED; return -1; } + + if (!blocking) + i--; } - run_mode = RUN_HALTED; + return 1; } return 0; @@ -551,7 +559,7 @@ static int sim_getregs(u_int16_t *regs) static int sim_setregs(const u_int16_t *regs) { memcpy(sim_regs, regs, sizeof(sim_regs)); - return -1; + return 0; } static int sim_readmem(u_int16_t addr, u_int8_t *mem, int len)