core: Deinitialize GDB in avr_terminate()
[simavr] / simavr / sim / sim_gdb.c
index 751f511..faa016f 100644 (file)
 #include <poll.h>
 #include <pthread.h>
 #include "sim_avr.h"
+#include "sim_core.h" // for SET_SREG_FROM, READ_SREG_INTO
+#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)
+{
+       /* 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)
 {
-    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;
+       w->len = 0;
 }
 
 static void gdb_send_reply(avr_gdb_t * g, char * cmd)
@@ -103,37 +189,17 @@ static void gdb_send_quick_status(avr_gdb_t * g, uint8_t signal)
        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;
 }
 
@@ -144,11 +210,12 @@ static int gdb_write_register(avr_gdb_t * g, int regi, uint8_t * src)
                        g->avr->data[regi] = *src;
                        return 1;
                case 32:
-                       g->avr->data[R_SREG] = * src;
+                       g->avr->data[R_SREG] = *src;
+                       SET_SREG_FROM(g->avr, *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);
@@ -163,8 +230,11 @@ static int gdb_read_register(avr_gdb_t * g, int regi, char * rep)
                case 0 ... 31:
                        sprintf(rep, "%02x", g->avr->data[regi]);
                        break;
-               case 32:
-                       sprintf(rep, "%02x", g->avr->data[R_SREG]);
+               case 32: {
+                               uint8_t sreg;
+                               READ_SREG_INTO(g->avr, sreg);
+                               sprintf(rep, "%02x", sreg);
+                       }
                        break;
                case 33:
                        sprintf(rep, "%02x%02x", g->avr->data[R_SPL], g->avr->data[R_SPH]);
@@ -218,10 +288,11 @@ static void gdb_handle_command(avr_gdb_t * g, char * cmd)
                        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;
@@ -230,10 +301,18 @@ static void gdb_handle_command(avr_gdb_t * g, char * cmd)
                                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
+                               AVR_LOG(avr, LOG_TRACE, "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);
+                               AVR_LOG(avr, LOG_ERROR, "GDB: read memory error %08x, %08x (ramend %04x)\n", addr, len, avr->ramend+1);
                                gdb_send_reply(g, "E01");
                                break;
                        }
@@ -265,7 +344,7 @@ static void gdb_handle_command(avr_gdb_t * g, char * cmd)
                                avr_ioctl(avr, AVR_IOCTL_EEPROM_SET, &ee);
                                gdb_send_reply(g, "OK");                                                        
                        } else {
-                               printf("write memory error %08x, %08x\n", addr, len);
+                               AVR_LOG(avr, LOG_ERROR, "GDB: write memory error %08x, %08x\n", addr, len);
                                gdb_send_reply(g, "E01");
                        }               
                }       break;
@@ -275,36 +354,52 @@ static void gdb_handle_command(avr_gdb_t * g, char * cmd)
                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;
        }
 }
 
-static int gdb_network_handler(avr_gdb_t * g, int dosleep)
+static int gdb_network_handler(avr_gdb_t * g, uint32_t dosleep)
 {
        fd_set read_set;
        int max;
@@ -317,7 +412,7 @@ static int gdb_network_handler(avr_gdb_t * g, int dosleep)
                FD_SET(g->listen, &read_set);
                max = g->listen + 1;
        }
-       struct timeval timo = { 0, dosleep ? 500 : 0 }; // short, but not too short interval
+       struct timeval timo = { 0, dosleep };   // short, but not too short interval
        int ret = select(max, &read_set, NULL, NULL, &timo);
 
        if (ret == 0)
@@ -337,7 +432,7 @@ static int gdb_network_handler(avr_gdb_t * g, int dosleep)
                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);
@@ -345,7 +440,8 @@ static int gdb_network_handler(avr_gdb_t * g, int dosleep)
                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;
@@ -385,21 +481,46 @@ static int gdb_network_handler(avr_gdb_t * g, int dosleep)
        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;
        }
@@ -416,7 +537,7 @@ int avr_gdb_init(avr_t * avr)
        avr->gdb = NULL;
 
        if ((g->listen = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
-               fprintf(stderr, "Can't create socket: %s", strerror(errno));
+               AVR_LOG(avr, LOG_ERROR, "GDB: Can't create socket: %s", strerror(errno));
                return -1;
        }
 
@@ -428,7 +549,7 @@ int avr_gdb_init(avr_t * avr)
        address.sin_port = htons (avr->gdb_port);
 
        if (bind(g->listen, (struct sockaddr *) &address, sizeof(address))) {
-               fprintf(stderr, "Can not bind socket: %s", strerror(errno));
+               AVR_LOG(avr, LOG_ERROR, "GDB: Can not bind socket: %s", strerror(errno));
                return -1;
        }
        if (listen(g->listen, 1)) {
@@ -439,6 +560,18 @@ int avr_gdb_init(avr_t * avr)
        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;
 }
+
+void avr_deinit_gdb(avr_t * avr)
+{
+       if (avr->gdb->listen != -1)
+          close(avr->gdb->listen);
+       if (avr->gdb->s != -1)
+          close(avr->gdb->s);
+       free(avr->gdb);
+}