From 57d5ea34d543c0515c598f0a91e15d6f095ac900 Mon Sep 17 00:00:00 2001 From: Daniel Beer Date: Mon, 6 Jun 2011 15:47:52 +1200 Subject: [PATCH] Implemented GDB client driver. --- Makefile | 2 +- gdb.c | 2 +- gdb_proto.c | 27 ++-- gdb_proto.h | 2 +- gdbc.c | 446 +++++++++++++++++++++++++++++++++++++++++++++++++++ gdbc.h | 27 ++++ main.c | 4 +- mspdebug.man | 9 ++ opdb.c | 11 ++ 9 files changed, 513 insertions(+), 17 deletions(-) create mode 100644 gdbc.c create mode 100644 gdbc.h diff --git a/Makefile b/Makefile index f27b842..df0ffac 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ mspdebug: main.o fet.o rf2500.o dis.o uif.o olimex.o ihex.o elf32.o stab.o \ fet_db.o usbutil.o titext.o srec.o device.o coff.o opdb.o output.o \ cmddb.o stdcmd.o prog.o flash_bsl.o list.o simio.o simio_tracer.o \ simio_timer.o simio_wdt.o simio_hwmult.o simio_gpio.o aliasdb.o \ - gdb_proto.o + gdb_proto.o gdbc.o $(CC) $(LDFLAGS) $(PORTS_LDFLAGS) -o $@ $^ -lusb $(READLINE_LIBS) .c.o: diff --git a/gdb.c b/gdb.c index f063398..3c1d563 100644 --- a/gdb.c +++ b/gdb.c @@ -288,7 +288,7 @@ static int run(struct gdb_data *data, char *buf) if (status == DEVICE_STATUS_INTR) goto out; - while (gdb_peek(data)) { + while (gdb_peek(data, 0)) { int c = gdb_getc(data); if (c < 0) diff --git a/gdb_proto.c b/gdb_proto.c index a16879c..f6bef3c 100644 --- a/gdb_proto.c +++ b/gdb_proto.c @@ -51,20 +51,20 @@ void gdb_printf(struct gdb_data *data, const char *fmt, ...) data->outlen += len; } -static int gdb_read(struct gdb_data *data, int blocking) +static int gdb_read(struct gdb_data *data, int timeout_ms) { fd_set r; int len; struct timeval to = { - .tv_sec = 0, - .tv_usec = 0 + .tv_sec = timeout_ms / 1000, + .tv_usec = timeout_ms % 1000 }; FD_ZERO(&r); FD_SET(data->sock, &r); if (select(data->sock + 1, &r, NULL, NULL, - blocking ? NULL : &to) < 0) { + timeout_ms < 0 ? NULL : &to) < 0) { pr_error("gdb: select"); return -1; } @@ -90,9 +90,9 @@ static int gdb_read(struct gdb_data *data, int blocking) return len; } -int gdb_peek(struct gdb_data *data) +int gdb_peek(struct gdb_data *data, int timeout_ms) { - if (data->head == data->tail && gdb_read(data, 0) < 0) + if (data->head == data->tail && gdb_read(data, timeout_ms) < 0) return -1; return data->head != data->tail; @@ -103,7 +103,7 @@ int gdb_getc(struct gdb_data *data) int c; /* If the buffer is empty, receive some more data */ - if (data->head == data->tail && gdb_read(data, 1) < 0) + if (data->head == data->tail && gdb_read(data, -1) < 0) return -1; c = data->xbuf[data->head]; @@ -128,20 +128,21 @@ int gdb_flush_ack(struct gdb_data *data) { int c; - do { - data->outbuf[data->outlen] = 0; #ifdef DEBUG_GDB - printc("-> %s\n", data->outbuf); + printc("-> %s\n", data->outbuf); #endif + data->outbuf[data->outlen] = 0; + + do { if (send(data->sock, data->outbuf, data->outlen, 0) < 0) { data->error = errno; pr_error("gdb: flush_ack"); return -1; } - c = gdb_getc(data); - if (c < 0) - return -1; + do { + c = gdb_getc(data); + } while (c != '+' && c != '-'); } while (c != '+'); data->outlen = 0; diff --git a/gdb_proto.h b/gdb_proto.h index 8f6b5e4..858cef9 100644 --- a/gdb_proto.h +++ b/gdb_proto.h @@ -39,7 +39,7 @@ void gdb_printf(struct gdb_data *data, const char *fmt, ...); int gdb_send(struct gdb_data *data, const char *msg); void gdb_packet_start(struct gdb_data *data); void gdb_packet_end(struct gdb_data *data); -int gdb_peek(struct gdb_data *data); +int gdb_peek(struct gdb_data *data, int timeout_ms); int gdb_getc(struct gdb_data *data); int gdb_flush_ack(struct gdb_data *data); int gdb_read_packet(struct gdb_data *data, char *buf); diff --git a/gdbc.c b/gdbc.c new file mode 100644 index 0000000..38dfcdc --- /dev/null +++ b/gdbc.c @@ -0,0 +1,446 @@ +/* MSPDebug - debugging tool for MSP430 MCUs + * Copyright (C) 2009-2011 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 "output.h" +#include "gdbc.h" +#include "gdb_proto.h" +#include "opdb.h" + +struct gdb_client { + struct device base; + struct gdb_data gdb; + int is_running; + + struct device_breakpoint last_bps[DEVICE_MAX_BREAKPOINTS]; +}; + +static int get_xfer_size(void) +{ + int x = opdb_get_numeric("gdbc_xfer_size"); + + if (x < 2) + return 2; + + if (x > GDB_MAX_XFER) + return GDB_MAX_XFER; + + return x; +} + +static int check_ok(struct gdb_data *gdb) +{ + char buf[GDB_BUF_SIZE]; + int len; + + len = gdb_read_packet(gdb, buf); + if (len < 0) + return -1; + + if (len < 1 || buf[0] == 'E') { + printc_err("gdbc: bad response: %s\n", buf); + return -1; + } + + return 0; +} + +static void gdbc_destroy(device_t dev_base) +{ + struct gdb_client *c = (struct gdb_client *)dev_base; + + shutdown(c->gdb.sock, 2); + close(c->gdb.sock); + free(c); +} + +static int gdbc_readmem(device_t dev_base, address_t addr, + uint8_t *mem, address_t len) +{ + struct gdb_client *dev = (struct gdb_client *)dev_base; + int xfer_size = get_xfer_size(); + char buf[GDB_BUF_SIZE]; + + while (len) { + int plen = len > xfer_size ? xfer_size : len; + int r; + int i; + + gdb_packet_start(&dev->gdb); + gdb_printf(&dev->gdb, "m%04x,%x", addr, plen); + gdb_packet_end(&dev->gdb); + if (gdb_flush_ack(&dev->gdb) < 0) + return -1; + + r = gdb_read_packet(&dev->gdb, buf); + if (r < 0) + return -1; + + if (r < plen * 2) { + printc_err("gdbc: short read at 0x%04x: expected %d " + "bytes, got %d\n", addr, plen, r / 2); + return -1; + } + + for (i = 0; i * 2 < r; i++) + mem[i] = (hexval(buf[i * 2]) << 4) | + hexval(buf[i * 2 + 1]); + + mem += plen; + len -= plen; + addr += plen; + } + + return 0; +} + +static int gdbc_writemem(device_t dev_base, address_t addr, + const uint8_t *mem, address_t len) +{ + struct gdb_client *dev = (struct gdb_client *)dev_base; + int xfer_size = get_xfer_size(); + + while (len) { + int plen = len > xfer_size ? xfer_size : len; + int i; + + gdb_packet_start(&dev->gdb); + gdb_printf(&dev->gdb, "M%04x,%x:", addr, plen); + for (i = 0; i < plen; i++) + gdb_printf(&dev->gdb, "%02x", mem[i]); + gdb_packet_end(&dev->gdb); + if (gdb_flush_ack(&dev->gdb) < 0) + return -1; + + if (check_ok(&dev->gdb) < 0) + return -1; + + mem += plen; + len -= plen; + addr += plen; + } + + return 0; +} + +static int gdbc_getregs(device_t dev_base, address_t *regs) +{ + struct gdb_client *dev = (struct gdb_client *)dev_base; + char buf[GDB_BUF_SIZE]; + int len; + int i; + + if (gdb_send(&dev->gdb, "g") < 0) + return -1; + + len = gdb_read_packet(&dev->gdb, buf); + if (len < 0) + return -1; + + if (len < DEVICE_NUM_REGS * 4) { + printc_err("gdbc: short read: expected %d chars, got %d\n", + DEVICE_NUM_REGS * 4, len); + return -1; + } + + for (i = 0; i < DEVICE_NUM_REGS; i++) { + char *text = buf + i * 4; + + regs[i] = (hexval(text[0]) << 4) | (hexval(text[1])) | + (hexval(text[2]) << 12) | (hexval(text[3]) << 8); + } + + return 0; +} + +static int gdbc_setregs(device_t dev_base, const address_t *regs) +{ + struct gdb_client *dev = (struct gdb_client *)dev_base; + int i; + + gdb_packet_start(&dev->gdb); + gdb_printf(&dev->gdb, "G"); + for (i = 0; i < DEVICE_NUM_REGS; i++) + gdb_printf(&dev->gdb, "%02x%02x", + regs[i] & 0xff, (regs[i] >> 8) & 0xff); + gdb_packet_end(&dev->gdb); + if (gdb_flush_ack(&dev->gdb) < 0) + return -1; + + return check_ok(&dev->gdb); +} + +static int do_reset(struct gdb_client *dev) +{ + char buf[GDB_BUF_SIZE]; + int len; + + if (gdb_send(&dev->gdb, "R00") < 0) + return -1; + + len = gdb_read_packet(&dev->gdb, buf); + if (!len) { + if (gdb_send(&dev->gdb, "r") < 0) + return -1; + len = gdb_read_packet(&dev->gdb, buf); + } + + if (len < 0) + return -1; + + if (len < 2 || buf[0] != 'O' || buf[1] != 'K') { + printc_err("gdbc: reset: bad response: %s\n", buf); + return -1; + } + + return 0; +} + +static int bp_send(struct gdb_data *gdb, int c, address_t addr) +{ + gdb_packet_start(gdb); + gdb_printf(gdb, "%c1,%04x,2", c, addr); + gdb_packet_end(gdb); + if (gdb_flush_ack(gdb) < 0) + return -1; + + return check_ok(gdb); +} + +static int refresh_bps(struct gdb_client *dev) +{ + int i; + + for (i = 0; i < dev->base.max_breakpoints; i++) { + struct device_breakpoint *bp = &dev->base.breakpoints[i]; + struct device_breakpoint *old = &dev->last_bps[i]; + + if (!(bp->flags & DEVICE_BP_DIRTY)) + continue; + + if ((old->flags & DEVICE_BP_ENABLED) && + (bp_send(&dev->gdb, 'z', old->addr) < 0)) + return -1; + + if ((bp->flags & DEVICE_BP_ENABLED) && + (bp_send(&dev->gdb, 'Z', bp->addr) < 0)) + return -1; + + bp->flags &= ~DEVICE_BP_DIRTY; + } + + memcpy(dev->last_bps, dev->base.breakpoints, sizeof(dev->last_bps)); + return 0; +} + +static int gdbc_ctl(device_t dev_base, device_ctl_t op) +{ + struct gdb_client *dev = (struct gdb_client *)dev_base; + + switch (op) { + case DEVICE_CTL_STEP: + if (gdb_send(&dev->gdb, "s") < 0) + return -1; + + return check_ok(&dev->gdb); + + case DEVICE_CTL_RUN: + if (refresh_bps(dev) < 0) + return -1; + if (gdb_send(&dev->gdb, "c") < 0) + return -1; + dev->is_running = 1; + return 0; + + case DEVICE_CTL_HALT: + if (dev->is_running) { + if (write_all(dev->gdb.sock, + (const uint8_t *)"\003", 1) < 0) { + pr_error("gdbc: write"); + return -1; + } + + dev->is_running = 0; + return check_ok(&dev->gdb); + } + return 0; + + case DEVICE_CTL_RESET: + return do_reset(dev); + } + + return 0; +} + +static int gdbc_erase(device_t dev_base, device_erase_type_t type, + address_t addr) +{ + struct gdb_client *dev = (struct gdb_client *)dev_base; + const char *cmd = "erase"; + char buf[GDB_BUF_SIZE]; + int len; + + gdb_packet_start(&dev->gdb); + gdb_printf(&dev->gdb, "qRcmd,"); + while (*cmd) + gdb_printf(&dev->gdb, "%02x", *(cmd++)); + gdb_packet_end(&dev->gdb); + + if (gdb_flush_ack(&dev->gdb) < 0) + return -1; + + len = gdb_read_packet(&dev->gdb, buf); + if (len < 0) + return -1; + + return 0; +} + +static device_status_t gdbc_poll(device_t dev_base) +{ + struct gdb_client *dev = (struct gdb_client *)dev_base; + char buf[GDB_BUF_SIZE]; + int len; + + if (!dev->is_running) + return DEVICE_STATUS_HALTED; + + len = gdb_peek(&dev->gdb, 50); + if (len < 0) { + if (errno == EINTR) + return DEVICE_STATUS_INTR; + + dev->is_running = 0; + return DEVICE_STATUS_ERROR; + } + + if (!len) + return DEVICE_STATUS_RUNNING; + + len = gdb_read_packet(&dev->gdb, buf); + if (len < 0) { + dev->is_running = 0; + return DEVICE_STATUS_ERROR; + } + + dev->is_running = 0; + return DEVICE_STATUS_HALTED; +} + +static int connect_to(const char *spec) +{ + const char *port_text; + int hn_len; + int port = 2000; + char hostname[128]; + struct hostent *ent; + struct sockaddr_in addr; + int sock; + + if (!spec) { + printc_err("gdbc: no remote target specified\n"); + return -1; + } + + port_text = strchr(spec, ':'); + if (port_text) { + port = atoi(port_text + 1); + hn_len = port_text - spec; + } else { + hn_len = strlen(spec); + } + + if (hn_len + 1 > sizeof(hostname)) + hn_len = sizeof(hostname) - 1; + memcpy(hostname, spec, hn_len); + hostname[hn_len] = 0; + + printc_dbg("Looking up %s...\n", hostname); + ent = gethostbyname(hostname); + if (!ent) { + printc_err("No such host: %s: %s\n", hostname, + hstrerror(h_errno)); + return -1; + } + + sock = socket(PF_INET, SOCK_STREAM, 0); + if (!sock) { + printc_err("socket: %s\n", strerror(errno)); + return -1; + } + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr = *(struct in_addr *)ent->h_addr; + printc_dbg("Connecting to %s:%d...\n", + inet_ntoa(addr.sin_addr), port); + + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + printc_err("connect: %s\n", strerror(errno)); + close(sock); + return -1; + } + + return sock; +} + +static device_t gdbc_open(const struct device_args *args) +{ + int sock = connect_to(args->path); + struct gdb_client *dev; + + if (sock < 0) + return NULL; + + dev = malloc(sizeof(struct gdb_client)); + if (!dev) { + printc_err("gdbc: can't allocate memory: %s\n", + strerror(errno)); + return NULL; + } + + memset(dev, 0, sizeof(*dev)); + dev->base.type = &device_gdbc; + dev->base.max_breakpoints = DEVICE_MAX_BREAKPOINTS; + + gdb_init(&dev->gdb, sock); + return (device_t)dev; +} + +const struct device_class device_gdbc = { + .name = "gdbc", + .help = "GDB client mode", + .open = gdbc_open, + .destroy = gdbc_destroy, + .readmem = gdbc_readmem, + .writemem = gdbc_writemem, + .erase = gdbc_erase, + .getregs = gdbc_getregs, + .setregs = gdbc_setregs, + .ctl = gdbc_ctl, + .poll = gdbc_poll +}; diff --git a/gdbc.h b/gdbc.h new file mode 100644 index 0000000..aa7f4c8 --- /dev/null +++ b/gdbc.h @@ -0,0 +1,27 @@ +/* MSPDebug - debugging tool for MSP430 MCUs + * Copyright (C) 2009-2011 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 GDBC_H_ +#define GDBC_H_ + +#include "device.h" + +/* GDB client implementation */ +extern const struct device_class device_gdbc; + +#endif diff --git a/main.c b/main.c index 22c7ecd..e8c6849 100644 --- a/main.c +++ b/main.c @@ -46,6 +46,7 @@ #include "vector.h" #include "fet_db.h" #include "flash_bsl.h" +#include "gdbc.h" #include "uif.h" #include "olimex.h" @@ -64,7 +65,8 @@ static const struct device_class *const driver_table[] = { &device_sim, &device_uif, &device_bsl, - &device_flash_bsl + &device_flash_bsl, + &device_gdbc }; static const char *version_text = diff --git a/mspdebug.man b/mspdebug.man index de22dc3..c2caa45 100644 --- a/mspdebug.man +++ b/mspdebug.man @@ -117,6 +117,11 @@ Connect RTS to the device's TEST pin and DTR to the device's RST pin. Use an appropriate serial level-shifter to make the connection, if necessary. If connecting to a device with non-multiplexed JTAG pins, connect RTS to the device's TCK pin via an inverter. +.IP "\fBgdbc\fR" +GDB client mode. Connect to a server which implements the GDB remote +protocol and provide an interface to it. To use this driver, specify +the remote address in \fIhostname:port\fR format using the \fB-d\fR +option. .SH COMMANDS MSPDebug can accept commands either through an interactive prompt, or non-interactively when specified on the command line. The supported @@ -608,6 +613,10 @@ speed, but may cause problems with some chips. Automatically restart the GDB server after disconnection. If this option is set, then the GDB server keeps running until an error occurs, or the user interrupts with Ctrl+C. +.IP "\fBgdbc_xfer_size\fR (numeric)" +Maximum size of memory transfers for the GDB client. Increasing this +value will result in faster transfers, but may cause problems with some +servers. .IP "\fBiradix\fR (numeric)" Default input radix for address expressions. For address values with no radix specifier, this value gives the input radix, which is diff --git a/opdb.c b/opdb.c index 1f28ee4..0e2e0f4 100644 --- a/opdb.c +++ b/opdb.c @@ -66,6 +66,17 @@ const static struct opdb_key keys[] = { .defval = { .numeric = 64 } + }, + { + .name = "gdbc_xfer_size", + .type = OPDB_TYPE_NUMERIC, + .help = +"Maximum size of memory transfers for the GDB client. Increasing this\n" +"value will result in faster transfers, but may cause problems with some\n" +"servers.\n", + .defval = { + .numeric = 64 + } } };