#include <poll.h>
#include <pthread.h>
#include "sim_avr.h"
+#include "sim_hex.h"
#include "avr_eeprom.h"
+#include "sim_gdb.h"
#define DBG(w)
+#define WATCH_LIMIT (32)
+
+typedef struct {
+ uint32_t len; /**< How many points are taken (points[0] .. points[len - 1]). */
+ struct {
+ uint32_t addr; /**< Which address is watched. */
+ uint32_t size; /**< How large is the watched segment. */
+ uint32_t kind; /**< Bitmask of enum avr_gdb_watch_type values. */
+ } points[WATCH_LIMIT];
+} avr_gdb_watchpoints_t;
+
typedef struct avr_gdb_t {
avr_t * avr;
int listen; // listen socket
int s; // current gdb connection
- uint32_t watchmap;
- struct {
- uint32_t pc;
- uint32_t len;
- int kind;
- } watch[32];
+ avr_gdb_watchpoints_t breakpoints;
+ avr_gdb_watchpoints_t watchpoints;
} avr_gdb_t;
- // decode line text hex to binary
-int read_hex_string(const char * src, uint8_t * buffer, int maxlen)
+
+/**
+ * Returns the index of the watchpoint if found, -1 otherwise.
+ */
+static int gdb_watch_find(const avr_gdb_watchpoints_t * w, uint32_t addr)
+{
+ for (int i = 0; i < w->len; i++) {
+ if (w->points[i].addr > addr) {
+ return -1;
+ } else if (w->points[i].addr == addr) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+/**
+ * Contrary to gdb_watch_find, this actually checks the address against
+ * a watched memory _range_.
+ */
+static int gdb_watch_find_range(const avr_gdb_watchpoints_t * w, uint32_t addr)
+{
+ for (int i = 0; i < w->len; i++) {
+ if (w->points[i].addr > addr) {
+ return -1;
+ } else if (w->points[i].addr <= addr && addr < w->points[i].addr + w->points[i].size) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+/**
+ * Returns -1 on error, 0 otherwise.
+ */
+static int gdb_watch_add_or_update(avr_gdb_watchpoints_t * w, enum avr_gdb_watch_type kind, uint32_t addr,
+ uint32_t size)
{
- uint8_t * dst = buffer;
- int ls = 0;
- uint8_t b = 0;
- while (*src && maxlen--) {
- char c = *src++;
- switch (c) {
- case 'a' ... 'f': b = (b << 4) | (c - 'a' + 0xa); break;
- case 'A' ... 'F': b = (b << 4) | (c - 'A' + 0xa); break;
- case '0' ... '9': b = (b << 4) | (c - '0'); break;
- default:
- if (c > ' ') {
- fprintf(stderr, "%s: huh '%c' (%s)\n", __FUNCTION__, c, src);
- return -1;
- }
- continue;
- }
- if (ls & 1) {
- *dst++ = b; b = 0;
- }
- ls++;
- }
-
- return dst - buffer;
+ /* If the watchpoint exists, update it. */
+ int i = gdb_watch_find(w, addr);
+ if (i != -1) {
+ w->points[i].size = size;
+ w->points[i].kind |= kind;
+ return 0;
+ }
+
+ /* Otherwise add it. */
+ if (w->len == WATCH_LIMIT) {
+ return -1;
+ }
+
+ /* Find the insertion point. */
+ for (i = 0; i < w->len; i++) {
+ if (w->points[i].addr > addr) {
+ break;
+ }
+ }
+
+ w->len++;
+
+ /* Make space for new element. */
+ for (int j = i + 1; j < w->len; j++) {
+ w->points[j] = w->points[j - 1];
+ }
+
+ /* Insert it. */
+ w->points[i].kind = kind;
+ w->points[i].addr = addr;
+ w->points[i].size = size;
+
+ return 0;
+}
+
+/**
+ * Returns -1 on error or if the specified point does not exist, 0 otherwise.
+ */
+static int gdb_watch_rm(avr_gdb_watchpoints_t * w, enum avr_gdb_watch_type kind, uint32_t addr)
+{
+ int i = gdb_watch_find(w, addr);
+ if (i == -1) {
+ return -1;
+ }
+
+ w->points[i].kind &= ~kind;
+ if (w->points[i].kind) {
+ return 0;
+ }
+
+ for (i = i + 1; i < w->len; i++) {
+ w->points[i - 1] = w->points[i];
+ }
+
+ w->len--;
+
+ return 0;
+}
+
+static void gdb_watch_clear(avr_gdb_watchpoints_t * w)
+{
+ w->len = 0;
}
static void gdb_send_reply(avr_gdb_t * g, char * cmd)
gdb_send_reply(g, cmd);
}
-static int gdb_change_breakpoint(avr_gdb_t * g, int set, int kind, uint32_t addr, uint32_t len)
+static int gdb_change_breakpoint(avr_gdb_watchpoints_t * w, int set, enum avr_gdb_watch_type kind,
+ uint32_t addr, uint32_t size)
{
- DBG(printf("set %d kind %d addr %08x len %d (map %08x)\n", set, kind, addr, len, g->watchmap);)
+ DBG(printf("set %d kind %d addr %08x len %d\n", set, kind, addr, len);)
+
if (set) {
- if (g->watchmap == 0xffffffff)
- return -1; // map full
-
- // check to see if it exists
- for (int i = 0; i < 32; i++)
- if ((g->watchmap & (1 << i)) && g->watch[i].pc == addr) {
- g->watch[i].len = len;
- return 0;
- }
- for (int i = 0; i < 32; i++)
- if (!(g->watchmap & (1 << i))) {
- g->watchmap |= (1 << i);
- g->watch[i].len = len;
- g->watch[i].pc = addr;
- g->watch[i].kind = kind;
- return 0;
- }
+ return gdb_watch_add_or_update(w, kind, addr, size);
} else {
- for (int i = 0; i < 32; i++)
- if ((g->watchmap & (1 << i)) && g->watch[i].pc == addr) {
- g->watchmap &= ~(1 << i);
- g->watch[i].len = 0;
- g->watch[i].pc = 0;
- g->watch[i].kind = 0;
- return 0;
- }
+ return gdb_watch_rm(w, kind, addr);
}
+
return -1;
}
g->avr->data[regi] = *src;
return 1;
case 32:
- g->avr->data[R_SREG] = * src;
+ g->avr->data[R_SREG] = *src;
return 1;
case 33:
- g->avr->data[R_SPL] = *src++;
- g->avr->data[R_SPH] = *src++;
+ g->avr->data[R_SPL] = src[0];
+ g->avr->data[R_SPH] = src[1];
return 2;
case 34:
g->avr->pc = src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
gdb_send_reply(g, "OK");
} break;
case 'm': { // read memory
- uint32_t addr, len;
+ avr_flashaddr_t addr;
+ uint32_t len;
sscanf(cmd, "%x,%x", &addr, &len);
uint8_t * src = NULL;
- if (addr < 0xffff) {
+ if (addr < avr->flashend) {
src = avr->flash + addr;
} else if (addr >= 0x800000 && (addr - 0x800000) <= avr->ramend) {
src = avr->data + addr - 0x800000;
avr_ioctl(avr, AVR_IOCTL_EEPROM_GET, &ee);
if (ee.ee)
src = ee.ee;
- else
+ else {
gdb_send_reply(g, "E01");
+ break;
+ }
+ } else if (addr >= 0x800000 && (addr - 0x800000) == avr->ramend+1 && len == 2) {
+ // Allow GDB to read a value just after end of stack.
+ // This is necessary to make instruction stepping work when stack is empty
+ printf("GDB read just past end of stack %08x, %08x; returning zero\n", addr, len);
+ gdb_send_reply(g, "0000");
+ break;
} else {
printf("read memory error %08x, %08x (ramend %04x)\n", addr, len, avr->ramend+1);
gdb_send_reply(g, "E01");
case 's': { // step
avr->state = cpu_Step;
} break;
+ case 'r': { // deprecated, suggested for AVRStudio compatibility
+ avr->state = cpu_StepDone;
+ avr_reset(avr);
+ } break;
case 'Z': // set clear break/watchpoint
case 'z': {
uint32_t kind, addr, len;
+ int set = (command == 'Z');
sscanf(cmd, "%d,%x,%x", &kind, &addr, &len);
-// printf("breakbpoint %d, %08x, %08x\n", kind, addr, len);
+// printf("breakpoint %d, %08x, %08x\n", kind, addr, len);
switch (kind) {
case 0: // software breakpoint
case 1: // hardware breakpoint
- if (addr <= avr->flashend) {
- if (gdb_change_breakpoint(g, command == 'Z', kind, addr, len))
- gdb_send_reply(g, "E01");
- else
- gdb_send_reply(g, "OK");
- } else
- gdb_send_reply(g, "E01"); // out of flash address
+ if (addr > avr->flashend ||
+ gdb_change_breakpoint(&g->breakpoints, set, 1 << kind, addr, len) == -1) {
+ gdb_send_reply(g, "E01");
+ break;
+ }
+
+ gdb_send_reply(g, "OK");
break;
- // TODO
case 2: // write watchpoint
case 3: // read watchpoint
case 4: // access watchpoint
+ /* Mask out the offset applied to SRAM addresses. */
+ addr &= ~0x800000;
+ if (addr > avr->ramend ||
+ gdb_change_breakpoint(&g->watchpoints, set, 1 << kind, addr, len) == -1) {
+ gdb_send_reply(g, "E01");
+ break;
+ }
+
+ gdb_send_reply(g, "OK");
+ break;
default:
gdb_send_reply(g, "");
- }
+ break;
+ }
} break;
default:
gdb_send_reply(g, "");
+ break;
}
}
printf("%s connection opened\n", __FUNCTION__);
}
- if (FD_ISSET(g->s, &read_set)) {
+ if (g->s != -1 && FD_ISSET(g->s, &read_set)) {
uint8_t buffer[1024];
ssize_t r = recv(g->s, buffer, sizeof(buffer)-1, 0);
if (r == 0) {
printf("%s connection closed\n", __FUNCTION__);
close(g->s);
- g->watchmap = 0; // clear breakpoints
+ gdb_watch_clear(&g->breakpoints);
+ gdb_watch_clear(&g->watchpoints);
g->avr->state = cpu_Running; // resume
g->s = -1;
return 1;
return 1;
}
+/**
+ * If an applicable watchpoint exists for addr, stop the cpu and send a status report.
+ * type is one of AVR_GDB_WATCH_READ, AVR_GDB_WATCH_WRITE depending on the type of access.
+ */
+void avr_gdb_handle_watchpoints(avr_t * avr, uint16_t addr, enum avr_gdb_watch_type type)
+{
+ avr_gdb_t *g = avr->gdb;
+
+ int i = gdb_watch_find_range(&g->watchpoints, addr);
+ if (i == -1) {
+ return;
+ }
+
+ int kind = g->watchpoints.points[i].kind;
+ if (kind & type) {
+ /* Send gdb reply (see GDB user manual appendix E.3). */
+ char cmd[78];
+ sprintf(cmd, "T%02x20:%02x;21:%02x%02x;22:%02x%02x%02x00;%s:%06x;",
+ 5, g->avr->data[R_SREG],
+ g->avr->data[R_SPL], g->avr->data[R_SPH],
+ g->avr->pc & 0xff, (g->avr->pc>>8)&0xff, (g->avr->pc>>16)&0xff,
+ kind & AVR_GDB_WATCH_ACCESS ? "awatch" : kind & AVR_GDB_WATCH_WRITE ? "watch" : "rwatch",
+ addr | 0x800000);
+ gdb_send_reply(g, cmd);
+
+ avr->state = cpu_Stopped;
+ }
+}
+
int avr_gdb_processor(avr_t * avr, int sleep)
{
if (!avr || !avr->gdb)
return 0;
avr_gdb_t * g = avr->gdb;
- if (g->watchmap && avr->state == cpu_Running) {
- for (int i = 0; i < 32; i++)
- if ((g->watchmap & (1 << i)) && g->watch[i].pc == avr->pc) {
- DBG(printf("avr_gdb_processor hit breakpoint at %08x\n", avr->pc);)
- gdb_send_quick_status(g, 0);
- avr->state = cpu_Stopped;
- }
- }
- if (avr->state == cpu_StepDone) {
+ if (avr->state == cpu_Running && gdb_watch_find(&g->breakpoints, avr->pc) != -1) {
+ DBG(printf("avr_gdb_processor hit breakpoint at %08x\n", avr->pc);)
+ gdb_send_quick_status(g, 0);
+ avr->state = cpu_Stopped;
+ } else if (avr->state == cpu_StepDone) {
gdb_send_quick_status(g, 0);
avr->state = cpu_Stopped;
}
g->avr = avr;
g->s = -1;
avr->gdb = g;
+ // change default run behaviour to use the slightly slower versions
+ avr->run = avr_callback_run_gdb;
+ avr->sleep = avr_callback_sleep_gdb;
return 0;
}