From ee7434d5f74210dda9615629dcb6f40ffc943783 Mon Sep 17 00:00:00 2001
From: ga <ga@oldell.fish>
Date: Wed, 16 Mar 2022 22:05:49 +0000
Subject: [PATCH] Make the break instruction useful.  The previous
 implementation could never move past it, as gdb knows it is in ROM and will
 not replace it.  Distinguish hard and soft breaks.

---
 simavr/sim/sim_core.c | 12 +++---
 simavr/sim/sim_gdb.c  | 91 +++++++++++++++++++++++++++----------------
 simavr/sim/sim_gdb.h  |  1 +
 3 files changed, 64 insertions(+), 40 deletions(-)

diff --git a/simavr/sim/sim_core.c b/simavr/sim/sim_core.c
index 4dae91e..a1a9ac9 100644
--- a/simavr/sim/sim_core.c
+++ b/simavr/sim/sim_core.c
@@ -164,7 +164,8 @@ uint8_t avr_core_watch_read(avr_t *avr, uint16_t addr)
 				"CORE: *** Wrapping read address "
 				"PC=%04x SP=%04x O=%04x Address %04x %% %04x --> %04x\n"
 				FONT_DEFAULT,
-				avr->pc, _avr_sp_get(avr), _avr_flash_read16le(avr, avr->pc), addr, (avr->ramend + 1), addr % (avr->ramend + 1));
+				avr->pc, _avr_sp_get(avr), _avr_flash_read16le(avr, avr->pc),
+				addr, (avr->ramend + 1), addr % (avr->ramend + 1));
 		addr = addr % (avr->ramend + 1);
 	}
 
@@ -938,12 +939,9 @@ run_one_again:
 				case 0x9598: { // BREAK -- 1001 0101 1001 1000
 					STATE("break\n");
 					if (avr->gdb) {
-						// if gdb is on, we break here as in here
-						// and we do so until gdb restores the instruction
-						// that was here before
-						avr->state = cpu_StepDone;
-						new_pc = avr->pc;
-						cycle = 0;
+						// if gdb is on, break here.
+						avr->state = cpu_Stopped;
+						avr_gdb_handle_break(avr);
 					}
 				}	break;
 				case 0x95a8: { // WDR -- Watchdog Reset -- 1001 0101 1010 1000
diff --git a/simavr/sim/sim_gdb.c b/simavr/sim/sim_gdb.c
index 40ed524..6d6ef97 100644
--- a/simavr/sim/sim_gdb.c
+++ b/simavr/sim/sim_gdb.c
@@ -201,22 +201,41 @@ gdb_send_reply(
 	send(g->s, reply, dst - reply + 3, 0);
 }
 
+static void
+gdb_send_stop_status(
+		avr_gdb_t  * g,
+		uint8_t     signal,
+		const char * reason,
+		uint32_t   * pp )
+{
+	avr_t   * avr;
+	uint8_t   sreg;
+	int       n;
+	char      cmd[64];
+
+	avr = g->avr;
+	READ_SREG_INTO(avr, sreg);
+
+	n = sprintf(cmd, "T%02x20:%02x;21:%02x%02x;22:%02x%02x%02x00;",
+				signal, sreg,
+				avr->data[R_SPL], avr->data[R_SPH],
+				avr->pc & 0xff, (avr->pc >> 8) & 0xff,
+				(avr->pc >> 16) & 0xff);
+	if (reason) {
+		if (pp)
+			sprintf(cmd + n, "%s:%x;", reason, *pp);
+		else
+			sprintf(cmd + n, "%s:;", reason);
+	}
+	gdb_send_reply(g, cmd);
+}
+
 static void
 gdb_send_quick_status(
 		avr_gdb_t * g,
 		uint8_t signal )
 {
-	char cmd[64];
-	uint8_t sreg;
-
-	READ_SREG_INTO(g->avr, sreg);
-
-	sprintf(cmd, "T%02x20:%02x;21:%02x%02x;22:%02x%02x%02x00;",
-		signal, 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);
-	gdb_send_reply(g, cmd);
+	gdb_send_stop_status(g, signal, NULL, NULL);
 }
 
 static int
@@ -227,14 +246,14 @@ gdb_change_breakpoint(
 		uint32_t addr,
 		uint32_t size )
 {
-	DBG(printf("set %d kind %d addr %08x len %d\n", set, kind, addr, size);)
+	DBG(printf("%s kind %d addr %08x len %d\n", set ? "Set" : "Clear",
+			   kind, addr, size);)
 
 	if (set) {
 		return gdb_watch_add_or_update(w, kind, addr, size);
 	} else {
 		return gdb_watch_rm(w, kind, addr);
 	}
-
 	return -1;
 }
 
@@ -500,9 +519,9 @@ gdb_handle_command(
 			if (strncmp(cmd, "Supported", 9) == 0) {
 				/* If GDB asked what features we support, report back
 				 * the features we support, which is just memory layout
-				 * information for now.
+				 * information and stop reasons for now.
 				 */
-				gdb_send_reply(g, "qXfer:memory-map:read+");
+				gdb_send_reply(g, "qXfer:memory-map:read+;swbreak+;hwbreak+");
 				break;
 			} else if (strncmp(cmd, "Attached", 8) == 0) {
 				/* Respond that we are attached to an existing process..
@@ -715,16 +734,19 @@ gdb_handle_command(
 					break;
 			}
 		}	break;
-		case 'k': 	// kill
-			avr->state = cpu_Done;
-			gdb_send_reply(g, "OK");
-			break;
 		case 'D': 	// detach
-			avr->state = cpu_Running;
+#ifdef DETACHABLE
+			if (avr->state = cpu_Stopped)
+				avr->state = cpu_Running;
 			gdb_send_reply(g, "OK");
 			close(g->s);
 			g->s = -1;
 			break;
+#endif
+		case 'k': 	// kill
+			avr->state = cpu_Done;
+			gdb_send_reply(g, "OK");
+			break;
 		case 'v':
 			handle_v(avr, g, cmd, length);
 			break;
@@ -828,6 +850,14 @@ gdb_network_handler(
 	return 1;
 }
 
+/* Called on a hardware break instruction. */
+void avr_gdb_handle_break(avr_t *avr)
+{
+	avr_gdb_t *g = avr->gdb;
+
+	gdb_send_stop_status(g, 5, "hwbreak", NULL);
+}
+
 /**
  * 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.
@@ -839,6 +869,7 @@ avr_gdb_handle_watchpoints(
 		enum avr_gdb_watch_type type )
 {
 	avr_gdb_t *g = avr->gdb;
+    uint32_t   false_addr;
 
 	int i = gdb_watch_find_range(&g->watchpoints, addr);
 	if (i == -1) {
@@ -850,19 +881,13 @@ avr_gdb_handle_watchpoints(
 			   addr, i, g->watchpoints.points[i].size, kind, type);)
 	if (kind & type) {
 		/* Send gdb reply (see GDB user manual appendix E.3). */
-		char cmd[78];
-		uint8_t sreg;
-
-		READ_SREG_INTO(g->avr, sreg);
-		sprintf(cmd, "T%02x20:%02x;21:%02x%02x;22:%02x%02x%02x00;%s:%06x;",
-				5, 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);
 
+		const char * what;
+
+		what = (kind & AVR_GDB_WATCH_ACCESS) ? "awatch" :
+			(kind & AVR_GDB_WATCH_WRITE) ? "watch" : "rwatch";
+		false_addr = addr + 0x800000;
+		gdb_send_stop_status(g, 5, what, &false_addr);
 		avr->state = cpu_Stopped;
 	}
 }
@@ -879,7 +904,7 @@ avr_gdb_processor(
 	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, 5);
+		gdb_send_stop_status(g, 5, "hwbreak", NULL);
 		avr->state = cpu_Stopped;
 	} else if (avr->state == cpu_StepDone) {
 		gdb_send_quick_status(g, 0);
diff --git a/simavr/sim/sim_gdb.h b/simavr/sim/sim_gdb.h
index 4ff6430..2936df7 100644
--- a/simavr/sim/sim_gdb.h
+++ b/simavr/sim/sim_gdb.h
@@ -46,6 +46,7 @@ int avr_gdb_processor(avr_t * avr, int sleep);
 
 // Called from sim_core.c
 void avr_gdb_handle_watchpoints(avr_t * g, uint16_t addr, enum avr_gdb_watch_type type);
+void avr_gdb_handle_break(avr_t *);
 
 #ifdef __cplusplus
 };
-- 
2.39.5