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