commit 709c5f44f7d828d549387d944186309923fcf6ff
parent 9de704baccb3c82ed2ec62e2a4c241a52c437433
Author: Brian Swetland <swetland@frotz.net>
Date: Thu, 9 Jul 2015 16:33:57 -0700
gdb-bridge: support for debugging lk threads
Diffstat:
M | tools/gdb-bridge.c | | | 294 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
1 file changed, 286 insertions(+), 8 deletions(-)
diff --git a/tools/gdb-bridge.c b/tools/gdb-bridge.c
@@ -42,9 +42,53 @@
#define S_CHK1 2
#define S_CHK2 3
-#define F_ACK 1
-#define F_RUNNING 4
-#define F_CONSOLE 8
+#define F_ACK 0x01
+#define F_RUNNING 0x04
+#define F_CONSOLE 0x08
+#define F_LK_THREADS 0x10
+
+#define DI_MAGIC 0x52474244
+#define DI_OFF_MAGIC 32
+#define DI_OFF_PTR 36
+
+#define LIST_OFF_PREV 0
+#define LIST_OFF_NEXT 4
+
+#define LK_MAX_STATE 5
+static char *lkstate[] = {
+ "SUSP ",
+ "READY",
+ "RUN ",
+ "BLOCK",
+ "SLEEP",
+ "DEAD ",
+ "?????",
+};
+
+typedef struct lkdebuginfo {
+ u32 version;
+ u32 thread_list_ptr;
+ u32 current_thread_ptr;
+ u8 off_list_node;
+ u8 off_state;
+ u8 off_saved_sp;
+ u8 off_was_preempted;
+ u8 off_name;
+ u8 off_waitq;
+} lkdebuginfo_t;
+
+typedef struct lkthread {
+ struct lkthread *next;
+ int active;
+ u32 threadptr;
+ u32 nextptr;
+ u32 state;
+ u32 saved_sp;
+ u32 preempted;
+ u32 waitq;
+ char name[32];
+ u32 regs[17];
+} lkthread_t;
struct gdbcnxn {
int fd;
@@ -57,6 +101,9 @@ struct gdbcnxn {
unsigned char rxbuf[MAXPKT];
unsigned char txbuf[MAXPKT];
char chk[4];
+ lkthread_t *threadlist;
+ lkthread_t *gselected;
+ lkthread_t *cselected;
};
void zprintf(const char *fmt, ...) {
@@ -77,6 +124,7 @@ void gdb_init(struct gdbcnxn *gc, int fd) {
gc->txptr = gc->txbuf;
gc->rxptr = gc->rxbuf;
gc->chk[2] = 0;
+ gc->threadlist = NULL;
}
static inline int rx_full(struct gdbcnxn *gc) {
@@ -264,18 +312,45 @@ static const char *target_xml =
static void handle_query(struct gdbcnxn *gc, char *cmd, char *args) {
if (!strcmp(cmd, "fThreadInfo")) {
- /* report just one thread id, #1, for now */
- gdb_puts(gc, "m1");
+ if (gc->threadlist) {
+ char tmp[32];
+ lkthread_t *t = gc->threadlist;
+ sprintf(tmp, "m%x", t->threadptr);
+ gdb_puts(gc, tmp);
+ for (t = t->next; t != NULL; t = t->next) {
+ sprintf(tmp, ",%x",t->threadptr);
+ gdb_puts(gc, tmp);
+ }
+ } else {
+ /* report just one thread id, #1, for now */
+ gdb_puts(gc, "m1");
+ }
} else if(!strcmp(cmd, "sThreadInfo")) {
/* no additional thread ids */
gdb_puts(gc, "l");
} else if(!strcmp(cmd, "ThreadExtraInfo")) {
+ u32 n = strtoul(args, NULL, 16);
+ lkthread_t *t;
/* gdb manual suggest 'Runnable', 'Blocked on Mutex', etc */
/* informational text shown in gdb's "info threads" listing */
+ for (t = gc->threadlist; t != NULL; t = t->next) {
+ if (t->threadptr == n) {
+ char tmp[128];
+ sprintf(tmp, "%s - %s", lkstate[t->state], t->name);
+ gdb_puthex(gc, tmp, strlen(tmp));
+ return;
+ }
+ }
gdb_puthex(gc, "Native", 6);
} else if(!strcmp(cmd, "C")) {
/* current thread ID */
- gdb_puts(gc, "QC1");
+ if (gc->cselected) {
+ char tmp[32];
+ sprintf(tmp, "QC%x", gc->cselected->threadptr);
+ gdb_puts(gc, tmp);
+ } else {
+ gdb_puts(gc, "QC1");
+ }
} else if (!strcmp(cmd, "Rcmd")) {
char *p = args;
cmd = p;
@@ -509,6 +584,150 @@ int handle_breakpoint(int add, u32 addr, u32 kind) {
}
}
+void dump_lk_thread(lkthread_t *t) {
+ xprintf("thread: @%08x sp=%08x wq=%08x st=%d name='%s'\n",
+ t->threadptr, t->saved_sp, t->waitq, t->state, t->name);
+ xprintf(" r0 %08x r4 %08x r8 %08x ip %08x\n",
+ t->regs[0], t->regs[4], t->regs[8], t->regs[12]);
+ xprintf(" r1 %08x r5 %08x r9 %08x sp %08x\n",
+ t->regs[1], t->regs[5], t->regs[9], t->regs[13]);
+ xprintf(" r2 %08x r6 %08x 10 %08x lr %08x\n",
+ t->regs[2], t->regs[6], t->regs[10], t->regs[14]);
+ xprintf(" r3 %08x r7 %08x 11 %08x pc %08x\n",
+ t->regs[3], t->regs[7], t->regs[11], t->regs[15]);
+}
+
+void dump_lk_threads(lkthread_t *t) {
+ while (t != NULL) {
+ dump_lk_thread(t);
+ t = t->next;
+ }
+}
+
+#define LT_NEXT_PTR(di,tp) ((tp) + di->off_list_node + LIST_OFF_NEXT)
+#define LT_STATE(di,tp) ((tp) + di->off_state)
+#define LT_SAVED_SP(di,tp) ((tp) + di->off_saved_sp)
+#define LT_NAME(di,tp) ((tp) + di->off_name)
+#define LT_WAITQ(di,tp) ((tp) + di->off_waitq)
+
+#define LIST_TO_THREAD(di,lp) ((lp) - (di)->off_list_node)
+
+lkthread_t *read_lk_thread(lkdebuginfo_t *di, u32 ptr, int active) {
+ lkthread_t *t = calloc(1, sizeof(lkthread_t));
+ int n;
+ if (t == NULL) goto fail;
+ t->threadptr = ptr;
+ if (swdp_ahb_read(LT_NEXT_PTR(di,ptr), &t->nextptr)) goto fail;
+ if (swdp_ahb_read(LT_STATE(di,ptr), &t->state)) goto fail;
+ if (swdp_ahb_read(LT_SAVED_SP(di,ptr), &t->saved_sp)) goto fail;
+ if (swdp_ahb_read(LT_WAITQ(di,ptr), &t->waitq)) goto fail;
+ if (swdp_ahb_read32(LT_NAME(di,ptr), (void*) t->name, 32 / 4)) goto fail;
+ t->name[31] = 0;
+ for (n = 0; n < 31; n++) {
+ if ((t->name[n] < ' ') || (t->name[n] > 127)) {
+ if (t->name[n] == 0) break;
+ t->name[n] = '.';
+ }
+ }
+ if (t->state > LK_MAX_STATE) t->state = LK_MAX_STATE + 1;
+ memset(t->regs, 0xee, sizeof(t->regs));
+ // lk arm-m context frame: R4 R5 R6 R7 R8 R9 R10 R11 LR
+ // if LR is FFFFFFxx then: R0 R1 R2 R3 R12 LR PC PSR
+ t->active = active;
+ if (!active) {
+ u32 fr[9];
+ if (swdp_ahb_read32(t->saved_sp, (void*) fr, 9)) goto fail;
+ memcpy(t->regs + 4, fr, 8 * sizeof(u32));
+ if ((fr[8] & 0xFFFFFF00) == 0xFFFFFF00) {
+ if (swdp_ahb_read32(t->saved_sp + 9 * sizeof(u32), (void*) fr, 8)) goto fail;
+ memcpy(t->regs + 0, fr, 4 * sizeof(u32));
+ t->regs[12] = fr[4];
+ t->regs[13] = t->saved_sp + 17 * sizeof(u32);
+ t->regs[14] = fr[5];
+ t->regs[15] = fr[6];
+ t->regs[16] = fr[7];
+ } else {
+ t->regs[13] = t->saved_sp + 9 * sizeof(u32);
+ t->regs[15] = fr[8];
+ t->regs[16] = 0x10000000;
+ }
+ }
+ return t;
+fail:
+ free(t);
+ return NULL;
+}
+
+void free_lk_threads(lkthread_t *list) {
+ lkthread_t *t, *next;
+ for (t = list; t != NULL; t = next) {
+ next = t->next;
+ free(t);
+ }
+}
+
+lkthread_t *find_lk_threads(int verbose) {
+ lkdebuginfo_t di;
+ lkthread_t *list = NULL;
+ lkthread_t *current = NULL;
+ lkthread_t *t;
+ u32 x;
+ u32 rtp;
+ if (swdp_ahb_read(DI_OFF_MAGIC, &x)) goto fail;
+ if (x != DI_MAGIC) {
+ if (verbose) xprintf("debuginfo: bad magic\n");
+ goto fail;
+ }
+ if (swdp_ahb_read(DI_OFF_PTR, &x)) goto fail;
+ if (x & 3) goto fail;
+ if (verbose) xprintf("debuginfo @ %08x\n", x);
+ if (swdp_ahb_read32(x, (void*) &di, sizeof(di) / 4)) goto fail;
+ if (verbose) {
+ xprintf("di %08x %08x %08x %d %d %d %d %d %d\n",
+ di.version, di.thread_list_ptr, di.current_thread_ptr,
+ di.off_list_node, di.off_state, di.off_saved_sp,
+ di.off_was_preempted, di.off_name, di.off_waitq);
+ }
+ if (di.version != 0x0100) {
+ if (verbose) xprintf("debuginfo: unsupported version\n");
+ goto fail;
+ }
+ if (swdp_ahb_read(di.current_thread_ptr, &x)) goto fail;
+ current = read_lk_thread(&di, x, 1);
+ rtp = di.thread_list_ptr;
+ for (;;) {
+ if (swdp_ahb_read(rtp + LIST_OFF_NEXT, &rtp)) goto fail;
+ if (rtp == di.thread_list_ptr) break;
+ x = LIST_TO_THREAD(&di, rtp);
+ if (current->threadptr == x) continue;
+ t = read_lk_thread(&di, x, 0);
+ t->next = list;
+ list = t;
+ }
+ current->next = list;
+ return current;
+fail:
+ if (current) free(current);
+ free_lk_threads(list);
+ return NULL;
+}
+
+void gdb_update_threads(struct gdbcnxn *gc) {
+ zprintf("GDB: sync threadlist\n");
+ free_lk_threads(gc->threadlist);
+ if (gc->flags & F_LK_THREADS) {
+ if ((gc->threadlist = find_lk_threads(0)) == NULL) {
+ zprintf("GDB: problem syncing threadlist\n");
+ }
+ gc->cselected = gc->threadlist;
+ gc->gselected = gc->threadlist;
+ } else {
+ gc->threadlist = NULL;
+ gc->cselected = NULL;
+ gc->gselected = NULL;
+ }
+}
+
void handle_command(struct gdbcnxn *gc, unsigned char *cmd) {
union {
u32 w[256+2];
@@ -544,11 +763,44 @@ void handle_command(struct gdbcnxn *gc, unsigned char *cmd) {
gdb_puts(gc, "S00");
gc->flags &= (~F_RUNNING);
swdp_core_halt();
+ gdb_update_threads(gc);
break;
case 'H':
+ if ((cmd[2] == '-') && (cmd[3] == '1')) {
+ zprintf("GDB: selected -1??\n");
+ } else {
+ lkthread_t *t;
+ u32 n = strtoul((char*) cmd + 2, NULL, 16);
+ for (t = gc->threadlist; t != NULL; t = t->next) {
+ if (t->threadptr == n) {
+ zprintf("GDB: selected %c tptr=%x\n", cmd[1], n);
+ if (cmd[1] == 'g') gc->gselected = t;
+ if (cmd[1] == 'c') gc->cselected = t;
+ goto hdone;
+ }
+ }
+ }
/* select thread - we've only got one */
+ if (cmd[1] == 'g') gc->gselected = gc->threadlist;
+ if (cmd[1] == 'c') gc->cselected = gc->threadlist;
+ hdone:
gdb_puts(gc, "OK");
break;
+ // is thread alive?
+ case 'T': {
+ lkthread_t *t;
+ n = strtoul((char*) cmd + 1, NULL, 16);
+ for (t = gc->threadlist; t != NULL; t = t->next) {
+ if (t->threadptr == n) {
+ break;
+ }
+ }
+ if (t) {
+ gdb_puts(gc, "OK");
+ } else {
+ gdb_puts(gc, "E00");
+ }
+ }
// m hexaddr , hexcount
// read from memory
case 'm':
@@ -582,13 +834,22 @@ void handle_command(struct gdbcnxn *gc, unsigned char *cmd) {
// read registers 0...
case 'g': {
u32 regs[19];
- swdp_core_read_all(regs);
+ if (gc->gselected && !gc->gselected->active) {
+ memset(regs, 0, sizeof(regs));
+ memcpy(regs, gc->gselected->regs, sizeof(gc->gselected->regs));
+ } else {
+ swdp_core_read_all(regs);
+ }
gdb_puthex(gc, regs, sizeof(regs));
break;
}
// G hexbytes
// write registers 0...
case 'G': {
+ if (gc->gselected && !gc->gselected->active) {
+ zprintf("GDB: attempting to write to inactive registers\n");
+ break;
+ }
int len = hextobin(gc->rxbuf, (char*) cmd + 1, MAXPKT);
for (n = 0; n < len / 4; n++) {
memcpy(&x, gc->rxbuf + (n * 4), sizeof(x));
@@ -601,7 +862,16 @@ void handle_command(struct gdbcnxn *gc, unsigned char *cmd) {
// read from register
case 'p': {
u32 v;
- swdp_core_read(strtoul((char*) cmd + 1, NULL, 16), &v);
+ u32 n = strtoul((char*) cmd + 1, NULL, 16);
+ if (gc->gselected && !gc->gselected->active) {
+ if (n > 16) {
+ v = 0xeeeeeeee;
+ } else {
+ v = gc->gselected->regs[n];
+ }
+ } else {
+ swdp_core_read(n, &v);
+ }
gdb_puthex(gc, &v, sizeof(v));
break;
}
@@ -610,6 +880,10 @@ void handle_command(struct gdbcnxn *gc, unsigned char *cmd) {
case 'P': {
int len;
char *data = strchr((char*) cmd + 1, '=');
+ if (gc->gselected && !gc->gselected->active) {
+ zprintf("GDB: attempting to write to inactive registers\n");
+ break;
+ }
if (data) {
*data++ = 0;
n = strtoul((char*) cmd + 1, NULL, 16);
@@ -624,6 +898,7 @@ void handle_command(struct gdbcnxn *gc, unsigned char *cmd) {
// halt (^c)
case '$':
swdp_core_halt();
+ gdb_update_threads(gc);
gc->flags &= (~F_RUNNING);
gdb_puts(gc, "S00");
break;
@@ -705,6 +980,8 @@ void gdb_server(int fd) {
zprintf("[ gdb connected ]\n");
debugger_unlock();
+ gc.flags |= F_LK_THREADS;
+
for (;;) {
fds[0].fd = fd;
@@ -727,6 +1004,7 @@ void gdb_server(int fd) {
gdb_prologue(&gc);
gdb_puts(&gc, "S00");
gdb_epilogue(&gc);
+ gdb_update_threads(&gc);
}
}
}