kgdb: add x86 HW breakpoints
Jason Wessel [Fri, 15 Feb 2008 20:55:56 +0000 (14:55 -0600)]
Add HW breakpoints into the arch specific portion of x86 kgdb.  In the
current x86 kernel.org kernels HW breakpoints are changed out in lazy
fashion because there is no infrastructure around changing them when
changing to a kernel task or entering the kernel mode via a system
call.  This lazy approach means that if a user process uses HW
breakpoints the kgdb will loose out.  This is an acceptable trade off
because the developer debugging the kernel is assumed to know what is
going on system wide and would be aware of this trade off.

There is a minor bug fix to the kgdb core so as to correctly call the
hw breakpoint functions with a valid value from the enum.

There is also a minor change to the x86_64 startup code when using
early HW breakpoints.  When the debugger is connected, the cpu startup
code must not zero out the HW breakpoint registers or you cannot hit
the breakpoints you are interested in, in the first place.

Signed-off-by: Jason Wessel <jason.wessel@windriver.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>

arch/x86/kernel/kgdb.c
arch/x86/kernel/setup64.c
kernel/kgdb.c

index 5d7a211..7d651ad 100644 (file)
@@ -182,6 +182,122 @@ void gdb_regs_to_pt_regs(unsigned long *gdb_regs, struct pt_regs *regs)
 #endif
 }
 
+static struct hw_breakpoint {
+       unsigned                enabled;
+       unsigned                type;
+       unsigned                len;
+       unsigned long           addr;
+} breakinfo[4];
+
+static void kgdb_correct_hw_break(void)
+{
+       unsigned long dr7;
+       int correctit = 0;
+       int breakbit;
+       int breakno;
+
+       get_debugreg(dr7, 7);
+       for (breakno = 0; breakno < 4; breakno++) {
+               breakbit = 2 << (breakno << 1);
+               if (!(dr7 & breakbit) && breakinfo[breakno].enabled) {
+                       correctit = 1;
+                       dr7 |= breakbit;
+                       dr7 &= ~(0xf0000 << (breakno << 2));
+                       dr7 |= ((breakinfo[breakno].len << 2) |
+                                breakinfo[breakno].type) <<
+                              ((breakno << 2) + 16);
+                       if (breakno >= 0 && breakno <= 3)
+                               set_debugreg(breakinfo[breakno].addr, breakno);
+
+               } else {
+                       if ((dr7 & breakbit) && !breakinfo[breakno].enabled) {
+                               correctit = 1;
+                               dr7 &= ~breakbit;
+                               dr7 &= ~(0xf0000 << (breakno << 2));
+                       }
+               }
+       }
+       if (correctit)
+               set_debugreg(dr7, 7);
+}
+
+static int
+kgdb_remove_hw_break(unsigned long addr, int len, enum kgdb_bptype bptype)
+{
+       int i;
+
+       for (i = 0; i < 4; i++)
+               if (breakinfo[i].addr == addr && breakinfo[i].enabled)
+                       break;
+       if (i == 4)
+               return -1;
+
+       breakinfo[i].enabled = 0;
+
+       return 0;
+}
+
+static void kgdb_remove_all_hw_break(void)
+{
+       int i;
+
+       for (i = 0; i < 4; i++)
+               memset(&breakinfo[i], 0, sizeof(struct hw_breakpoint));
+}
+
+static int
+kgdb_set_hw_break(unsigned long addr, int len, enum kgdb_bptype bptype)
+{
+       unsigned type;
+       int i;
+
+       for (i = 0; i < 4; i++)
+               if (!breakinfo[i].enabled)
+                       break;
+       if (i == 4)
+               return -1;
+
+       switch (bptype) {
+       case BP_HARDWARE_BREAKPOINT:
+               type = 0;
+               len  = 1;
+               break;
+       case BP_WRITE_WATCHPOINT:
+               type = 1;
+               break;
+       case BP_ACCESS_WATCHPOINT:
+               type = 3;
+               break;
+       default:
+               return -1;
+       }
+
+       if (len == 1 || len == 2 || len == 4)
+               breakinfo[i].len  = len - 1;
+       else
+               return -1;
+
+       breakinfo[i].enabled = 1;
+       breakinfo[i].addr = addr;
+       breakinfo[i].type = type;
+
+       return 0;
+}
+
+/**
+ *     kgdb_disable_hw_debug - Disable hardware debugging while we in kgdb.
+ *     @regs: Current &struct pt_regs.
+ *
+ *     This function will be called if the particular architecture must
+ *     disable hardware debugging while it is processing gdb packets or
+ *     handling exception.
+ */
+void kgdb_disable_hw_debug(struct pt_regs *regs)
+{
+       /* Disable hardware debugging while we are in kgdb: */
+       set_debugreg(0UL, 7);
+}
+
 /**
  *     kgdb_post_primary_code - Save error vector/code numbers.
  *     @regs: Original pt_regs.
@@ -243,6 +359,7 @@ int kgdb_arch_handle_exception(int e_vector, int signo, int err_code,
                               struct pt_regs *linux_regs)
 {
        unsigned long addr;
+       unsigned long dr6;
        char *ptr;
        int newPC;
 
@@ -269,6 +386,22 @@ int kgdb_arch_handle_exception(int e_vector, int signo, int err_code,
                        }
                }
 
+               get_debugreg(dr6, 6);
+               if (!(dr6 & 0x4000)) {
+                       int breakno;
+
+                       for (breakno = 0; breakno < 4; breakno++) {
+                               if (dr6 & (1 << breakno) &&
+                                   breakinfo[breakno].type == 0) {
+                                       /* Set restore flag: */
+                                       linux_regs->flags |= X86_EFLAGS_RF;
+                                       break;
+                               }
+                       }
+               }
+               set_debugreg(0UL, 6);
+               kgdb_correct_hw_break();
+
                return 0;
        }
 
@@ -426,4 +559,9 @@ unsigned long kgdb_arch_pc(int exception, struct pt_regs *regs)
 struct kgdb_arch arch_kgdb_ops = {
        /* Breakpoint instruction: */
        .gdb_bpt_instr          = { 0xcc },
+       .flags                  = KGDB_HW_BREAKPOINT,
+       .set_hw_breakpoint      = kgdb_set_hw_break,
+       .remove_hw_breakpoint   = kgdb_remove_hw_break,
+       .remove_all_hw_break    = kgdb_remove_all_hw_break,
+       .correct_hw_break       = kgdb_correct_hw_break,
 };
index e24c456..143aa78 100644 (file)
@@ -11,6 +11,7 @@
 #include <linux/bootmem.h>
 #include <linux/bitops.h>
 #include <linux/module.h>
+#include <linux/kgdb.h>
 #include <asm/pda.h>
 #include <asm/pgtable.h>
 #include <asm/processor.h>
@@ -327,6 +328,17 @@ void __cpuinit cpu_init (void)
        load_TR_desc();
        load_LDT(&init_mm.context);
 
+#ifdef CONFIG_KGDB
+       /*
+        * If the kgdb is connected no debug regs should be altered.  This
+        * is only applicable when KGDB and a KGDB I/O module are built
+        * into the kernel and you are using early debugging with
+        * kgdbwait. KGDB will control the kernel HW breakpoint registers.
+        */
+       if (kgdb_connected && arch_kgdb_ops.correct_hw_break)
+               arch_kgdb_ops.correct_hw_break();
+       else {
+#endif
        /*
         * Clear all 6 debug registers:
         */
@@ -337,6 +349,10 @@ void __cpuinit cpu_init (void)
        set_debugreg(0UL, 3);
        set_debugreg(0UL, 6);
        set_debugreg(0UL, 7);
+#ifdef CONFIG_KGDB
+       /* If the kgdb is connected no debug regs should be altered. */
+       }
+#endif
 
        fpu_init(); 
 
index 319c08c..68aea78 100644 (file)
@@ -1139,10 +1139,10 @@ static void gdb_cmd_break(struct kgdb_state *ks)
                error = kgdb_remove_sw_break(addr);
        else if (remcom_in_buffer[0] == 'Z')
                error = arch_kgdb_ops.set_hw_breakpoint(addr,
-                       (int)length, *bpt_type);
+                       (int)length, *bpt_type - '0');
        else if (remcom_in_buffer[0] == 'z')
                error = arch_kgdb_ops.remove_hw_breakpoint(addr,
-                       (int) length, *bpt_type);
+                       (int) length, *bpt_type - '0');
 
        if (error == 0)
                strcpy(remcom_out_buffer, "OK");