Implemented basic gdb support.

Tested only with simulation mode -- real devices need testing.
This commit is contained in:
Daniel Beer 2010-03-22 19:12:51 +13:00
parent 982ac20147
commit 39a4c5806b
9 changed files with 667 additions and 29 deletions

View File

@ -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:

2
bsl.c
View File

@ -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;
}

View File

@ -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);

8
fet.c
View File

@ -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;

575
gdb.c Normal file
View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <ctype.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#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;
}

29
gdb.h Normal file
View File

@ -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

29
main.c
View File

@ -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,
"= <expression>\n"
@ -638,6 +653,9 @@ static const struct command all_commands[] = {
{"dis", cmd_dis,
"dis <address> [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();

View File

@ -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

36
sim.c
View File

@ -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)