[U-Boot] [RFC PATCH v1 1/2] kgdb: add breakpoint support

Peng Fan Peng.Fan at freescale.com
Sat Nov 1 06:12:55 CET 2014


Add Z/z protocal support for breakpoint set/remove.

Signed-off-by: Peng Fan <Peng.Fan at freescale.com>
---
 common/kgdb.c  | 273 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 include/kgdb.h |  35 ++++++++
 2 files changed, 308 insertions(+)

diff --git a/common/kgdb.c b/common/kgdb.c
index d357463..fd83ccd 100644
--- a/common/kgdb.c
+++ b/common/kgdb.c
@@ -92,6 +92,8 @@
 #include <kgdb.h>
 #include <command.h>
 
+#include <asm-generic/errno.h>
+
 #undef KGDB_DEBUG
 
 /*
@@ -111,6 +113,17 @@ static int longjmp_on_fault = 0;
 static int kdebug = 1;
 #endif
 
+struct kgdb_bkpt kgdb_break[KGDB_MAX_BREAKPOINTS] = {
+	[0 ... KGDB_MAX_BREAKPOINTS-1] = { .state = BP_UNDEFINED }
+};
+
+#ifdef CONFIG_ARM
+unsigned char gdb_bpt_instr[4] = {0xfe, 0xde, 0xff, 0xe7};
+#else
+#error "Please implement gdb_bpt_instr!"
+#endif
+
+
 static const char hexchars[]="0123456789abcdef";
 
 /* Convert ch from a hex digit to an int */
@@ -309,6 +322,200 @@ putpacket(unsigned char *buffer)
 	} while ((recv & 0x7f) != '+');
 }
 
+int kgdb_validate_break_address(unsigned addr)
+{
+	/* Need More */
+	return 0;
+}
+
+static int probe_kernel_read(unsigned char *dst, void *src, size_t size)
+{
+	int i;
+	unsigned char *dst_ptr = dst;
+	unsigned char *src_ptr = src;
+
+	for (i = 0; i < size; i++)
+		*dst_ptr++ = *src_ptr++;
+
+	return 0;
+}
+
+static int probe_kernel_write(char *dst, void *src, size_t size)
+{
+	int i;
+	char *dst_ptr = dst;
+	char *src_ptr = src;
+
+	for (i = 0; i < size; i++)
+		*dst_ptr++ = *src_ptr++;
+
+	return 0;
+}
+
+__weak int kgdb_arch_set_breakpoint(struct kgdb_bkpt *bpt)
+{
+	int err;
+
+	err = probe_kernel_read(bpt->saved_instr, (char *)bpt->bpt_addr,
+				BREAK_INSTR_SIZE);
+	if (err)
+		return err;
+
+	err = probe_kernel_write((char *)bpt->bpt_addr, gdb_bpt_instr,
+				 BREAK_INSTR_SIZE);
+
+	return err;
+}
+
+/*
+ * Set the breakpoints whose state is BP_SET to BP_ACTIVE
+ */
+int kgdb_active_sw_breakpoints(void)
+{
+	int err;
+	int ret = 0;
+	int i;
+
+	for (i = 0; i < KGDB_MAX_BREAKPOINTS; i++) {
+		if (kgdb_break[i].state != BP_SET)
+			continue;
+
+		err = kgdb_arch_set_breakpoint(&kgdb_break[i]);
+		if (err) {
+			ret = err;
+			printf("KGDB: BP install failed: %lx\n",
+			       kgdb_break[i].bpt_addr);
+			continue;
+		}
+
+		kgdb_break[i].state = BP_ACTIVE;
+
+		/*
+		 * kgdb_arch_set_breakpoint touched dcache and memory.
+		 * cache should be flushed to let icache can see the updated
+		 * inst.
+		 */
+		/* flush work is done in do_exit */
+		kgdb_flush_cache_all();
+	}
+
+	return ret;
+}
+
+/*
+ * Set state from BP_SET to BP_REMOVED
+ */
+int kgdb_remove_sw_breakpoint(unsigned int addr)
+{
+	int i;
+
+	for (i = 0; i < KGDB_MAX_BREAKPOINTS; i++) {
+		if ((kgdb_break[i].state == BP_SET) &&
+		    (kgdb_break[i].bpt_addr == addr)) {
+			kgdb_break[i].state = BP_REMOVED;
+			return 0;
+		}
+	}
+
+	return -ENOENT;
+}
+
+int kgdb_set_sw_breakpoint(unsigned int addr)
+{
+	int err = kgdb_validate_break_address(addr);
+	int breakno = -1;
+	int i;
+
+	if (err)
+		return err;
+
+	for (i = 0; i < KGDB_MAX_BREAKPOINTS; i++) {
+		if ((kgdb_break[i].state == BP_SET) &&
+		    (kgdb_break[i].bpt_addr == addr))
+			return -EEXIST;
+	}
+
+	for (i = 0; i < KGDB_MAX_BREAKPOINTS; i++) {
+		/* removed or unused, use it */
+		if ((kgdb_break[i].state == BP_REMOVED) ||
+		    (kgdb_break[i].state == BP_UNDEFINED)) {
+			breakno = i;
+			break;
+		}
+	}
+
+	if (breakno == -1)
+		return -E2BIG;
+
+	kgdb_break[breakno].state = BP_SET;
+	kgdb_break[breakno].type = BP_BREAKPOINT;
+	kgdb_break[breakno].bpt_addr = addr;
+
+	return 0;
+}
+
+__weak int kgdb_arch_remove_breakpoint(struct kgdb_bkpt *bpt)
+{
+	return probe_kernel_write((char *)bpt->bpt_addr,
+				  (char *)bpt->saved_instr, BREAK_INSTR_SIZE);
+}
+
+/*
+ * set breakpoints whose state is BP_ACTIVE to BP_SET
+ */
+int kgdb_deactivate_sw_breakpoints(void)
+{
+	int err;
+	int ret = 0;
+	int i;
+
+	for (i = 0; i < KGDB_MAX_BREAKPOINTS; i++) {
+		if (kgdb_break[i].state != BP_ACTIVE)
+			continue;
+
+		err = kgdb_arch_remove_breakpoint(&kgdb_break[i]);
+		if (err) {
+			printf("KGDB: BP remove failed: %lx\n",
+			       kgdb_break[i].bpt_addr);
+			ret = err;
+		}
+
+		kgdb_break[i].state = BP_SET;
+
+		/*
+		 * kgdb_arch_remove_breakpoint touched dcache and memory.
+		 * cache should be flushed to let icache can see the updated
+		 * inst.
+		 */
+		kgdb_flush_cache_all();
+	}
+
+	return ret;
+}
+
+static int kgdb_remove_all_break(void)
+{
+	int err;
+	int i;
+
+	/* Clear memory breakpoints. */
+	for (i = 0; i < KGDB_MAX_BREAKPOINTS; i++) {
+		if (kgdb_break[i].state != BP_ACTIVE)
+			goto setundefined;
+		err = kgdb_arch_remove_breakpoint(&kgdb_break[i]);
+		if (err)
+			printf("KGDB: breakpoint remove failed: %lx\n",
+			       kgdb_break[i].bpt_addr);
+setundefined:
+		kgdb_break[i].state = BP_UNDEFINED;
+	}
+
+	/* clear hardware breakpoints. */
+	/* ToDo in future. */
+
+	return 0;
+}
+
 /*
  * This function does all command processing for interfacing to gdb.
  */
@@ -318,6 +525,7 @@ handle_exception (struct pt_regs *regs)
 	int addr;
 	int length;
 	char *ptr;
+	char *bpt_type;
 	kgdb_data kd;
 	int i;
 
@@ -376,6 +584,12 @@ handle_exception (struct pt_regs *regs)
 
 	putpacket((unsigned char *)&remcomOutBuffer);
 
+	/*
+	 * Each time trigger a kgdb break, first deactive all active
+	 * breakpoints.
+	 */
+	kgdb_deactivate_sw_breakpoints();
+
 	while (1) {
 		volatile int errnum;
 
@@ -394,6 +608,8 @@ handle_exception (struct pt_regs *regs)
 		if (errnum == 0) switch (remcomInBuffer[0]) {
 
 		case '?':               /* report most recent signal */
+			kgdb_remove_all_break();
+
 			remcomOutBuffer[0] = 'S';
 			remcomOutBuffer[1] = hexchars[kd.sigval >> 4];
 			remcomOutBuffer[2] = hexchars[kd.sigval & 0xf];
@@ -465,6 +681,8 @@ handle_exception (struct pt_regs *regs)
 				kd.extype |= KGDBEXIT_WITHADDR;
 			}
 
+			kgdb_active_sw_breakpoints();
+
 			goto doexit;
 
 		case 'S':    /* SSS  single step with signal SS */
@@ -479,6 +697,8 @@ handle_exception (struct pt_regs *regs)
 				kd.extype |= KGDBEXIT_WITHADDR;
 			}
 
+			kgdb_active_sw_breakpoints();
+
 		doexit:
 /* Need to flush the instruction cache here, as we may have deposited a
  * breakpoint, and the icache probably has no way of knowing that a data ref to
@@ -506,6 +726,59 @@ handle_exception (struct pt_regs *regs)
 				kgdb_error(KGDBERR_BADPARAMS);
 			}
 			break;
+		case 'z':
+			/*
+			 * Break point remove
+			 * packet: zt,addr,length
+			 */
+		case 'Z':
+			/*
+			 * Break point set
+			 * packet: Zt,addr,length
+			 *
+			 * t is type: `0' - software breakpoint,
+			 * `1' - hardware breakpoint, `2' - write watchpoint,
+			 * `3' - read watchpoint, `4' - access watchpoint;
+			 * addr is address; length is in bytes. For a software
+			 * breakpoint, length specifies the size of the
+			 * instruction to be patched. For hardware breakpoints
+			 * and watchpoints length specifies the memory region
+			 * to be monitored. To avoid potential problems with
+			 * duplicate packets, the operations should be
+			 * implemented in an idempotent way.
+			 */
+			bpt_type = &remcomInBuffer[1];
+			ptr = &remcomInBuffer[2];
+
+			if (*(ptr++) != ',') {
+				errnum = -EINVAL;
+				break;
+			}
+
+			if (!hexToInt(&ptr, &addr)) {
+				errnum = -EINVAL;
+				break;
+			}
+
+			if (*ptr++ != ',') {
+				errnum = -EINVAL;
+				break;
+			}
+
+			/* only software breakpoint is implemented */
+			if ((remcomInBuffer[0] == 'Z') && (*bpt_type == '0')) {
+				errnum = kgdb_set_sw_breakpoint(addr);
+			} else if ((remcomInBuffer[0] == 'z') &&
+				 (*bpt_type == '0')) {
+				errnum = kgdb_remove_sw_breakpoint(addr);
+			} else {
+				/* Unsupported */
+				errnum = -EINVAL;
+			}
+
+			if (errnum == 0)
+				strcpy(remcomOutBuffer, "OK");
+			break;
 		}			/* switch */
 
 		if (errnum != 0)
diff --git a/include/kgdb.h b/include/kgdb.h
index b6ba742..f9152b5 100644
--- a/include/kgdb.h
+++ b/include/kgdb.h
@@ -20,6 +20,12 @@
 
 #define KGDBEXIT_WITHADDR	0x100
 
+#if defined(CONFIG_ARM)
+#define BREAK_INSTR_SIZE	4
+#else
+#error "BREAK_INSTR_SIZE not set"
+#endif
+
 typedef
 	struct {
 		int num;
@@ -38,6 +44,29 @@ typedef
 	}
 kgdb_data;
 
+enum kgdb_bptype {
+	BP_BREAKPOINT = 0,
+	BP_HARDWARE_BREAKPOINT,
+	BP_WRITE_WATCHPOINT,
+	BP_READ_WATCHPOINT,
+	BP_ACCESS_WATCHPOINT,
+	BP_POKE_BREAKPOINT,
+};
+
+enum kgdb_bpstate {
+	BP_UNDEFINED = 0,
+	BP_REMOVED,
+	BP_SET,
+	BP_ACTIVE
+};
+
+struct kgdb_bkpt {
+	unsigned long		bpt_addr;
+	unsigned char		saved_instr[BREAK_INSTR_SIZE];
+	enum kgdb_bptype	type;
+	enum kgdb_bpstate	state;
+};
+
 /* these functions are provided by the generic kgdb support */
 extern void kgdb_init(void);
 extern void kgdb_error(int);
@@ -67,4 +96,10 @@ extern void kgdb_interruptible(int);
 /* this is referenced in the trap handler for the platform */
 extern int (*debugger_exception_handler)(struct pt_regs *);
 
+int kgdb_set_sw_break(unsigned int addr);
+int kgdb_remove_sw_break(unsigned int addr);
+int kgdb_validate_break_address(unsigned int addr);
+
+#define KGDB_MAX_BREAKPOINTS	32
+
 #endif /* __KGDB_H__ */
-- 
1.8.4.5



More information about the U-Boot mailing list