aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJason Wessel <jason.wessel@windriver.com>2008-02-15 15:55:56 -0500
committerIngo Molnar <mingo@elte.hu>2008-04-17 14:05:39 -0400
commit64e9ee3095b61d0300ea548216a57d2536611309 (patch)
tree21cf21caafa18b5661ceeaeb4d29e2ed784d0345
parent67baf94cd260dc37504dbd15ba3faa2d8cf8a444 (diff)
kgdb: add x86 HW breakpoints
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>
-rw-r--r--arch/x86/kernel/kgdb.c138
-rw-r--r--arch/x86/kernel/setup64.c16
-rw-r--r--kernel/kgdb.c4
3 files changed, 156 insertions, 2 deletions
diff --git a/arch/x86/kernel/kgdb.c b/arch/x86/kernel/kgdb.c
index 5d7a21119bf8..7d651adcb222 100644
--- a/arch/x86/kernel/kgdb.c
+++ b/arch/x86/kernel/kgdb.c
@@ -182,6 +182,122 @@ void gdb_regs_to_pt_regs(unsigned long *gdb_regs, struct pt_regs *regs)
182#endif 182#endif
183} 183}
184 184
185static struct hw_breakpoint {
186 unsigned enabled;
187 unsigned type;
188 unsigned len;
189 unsigned long addr;
190} breakinfo[4];
191
192static void kgdb_correct_hw_break(void)
193{
194 unsigned long dr7;
195 int correctit = 0;
196 int breakbit;
197 int breakno;
198
199 get_debugreg(dr7, 7);
200 for (breakno = 0; breakno < 4; breakno++) {
201 breakbit = 2 << (breakno << 1);
202 if (!(dr7 & breakbit) && breakinfo[breakno].enabled) {
203 correctit = 1;
204 dr7 |= breakbit;
205 dr7 &= ~(0xf0000 << (breakno << 2));
206 dr7 |= ((breakinfo[breakno].len << 2) |
207 breakinfo[breakno].type) <<
208 ((breakno << 2) + 16);
209 if (breakno >= 0 && breakno <= 3)
210 set_debugreg(breakinfo[breakno].addr, breakno);
211
212 } else {
213 if ((dr7 & breakbit) && !breakinfo[breakno].enabled) {
214 correctit = 1;
215 dr7 &= ~breakbit;
216 dr7 &= ~(0xf0000 << (breakno << 2));
217 }
218 }
219 }
220 if (correctit)
221 set_debugreg(dr7, 7);
222}
223
224static int
225kgdb_remove_hw_break(unsigned long addr, int len, enum kgdb_bptype bptype)
226{
227 int i;
228
229 for (i = 0; i < 4; i++)
230 if (breakinfo[i].addr == addr && breakinfo[i].enabled)
231 break;
232 if (i == 4)
233 return -1;
234
235 breakinfo[i].enabled = 0;
236
237 return 0;
238}
239
240static void kgdb_remove_all_hw_break(void)
241{
242 int i;
243
244 for (i = 0; i < 4; i++)
245 memset(&breakinfo[i], 0, sizeof(struct hw_breakpoint));
246}
247
248static int
249kgdb_set_hw_break(unsigned long addr, int len, enum kgdb_bptype bptype)
250{
251 unsigned type;
252 int i;
253
254 for (i = 0; i < 4; i++)
255 if (!breakinfo[i].enabled)
256 break;
257 if (i == 4)
258 return -1;
259
260 switch (bptype) {
261 case BP_HARDWARE_BREAKPOINT:
262 type = 0;
263 len = 1;
264 break;
265 case BP_WRITE_WATCHPOINT:
266 type = 1;
267 break;
268 case BP_ACCESS_WATCHPOINT:
269 type = 3;
270 break;
271 default:
272 return -1;
273 }
274
275 if (len == 1 || len == 2 || len == 4)
276 breakinfo[i].len = len - 1;
277 else
278 return -1;
279
280 breakinfo[i].enabled = 1;
281 breakinfo[i].addr = addr;
282 breakinfo[i].type = type;
283
284 return 0;
285}
286
287/**
288 * kgdb_disable_hw_debug - Disable hardware debugging while we in kgdb.
289 * @regs: Current &struct pt_regs.
290 *
291 * This function will be called if the particular architecture must
292 * disable hardware debugging while it is processing gdb packets or
293 * handling exception.
294 */
295void kgdb_disable_hw_debug(struct pt_regs *regs)
296{
297 /* Disable hardware debugging while we are in kgdb: */
298 set_debugreg(0UL, 7);
299}
300
185/** 301/**
186 * kgdb_post_primary_code - Save error vector/code numbers. 302 * kgdb_post_primary_code - Save error vector/code numbers.
187 * @regs: Original pt_regs. 303 * @regs: Original pt_regs.
@@ -243,6 +359,7 @@ int kgdb_arch_handle_exception(int e_vector, int signo, int err_code,
243 struct pt_regs *linux_regs) 359 struct pt_regs *linux_regs)
244{ 360{
245 unsigned long addr; 361 unsigned long addr;
362 unsigned long dr6;
246 char *ptr; 363 char *ptr;
247 int newPC; 364 int newPC;
248 365
@@ -269,6 +386,22 @@ int kgdb_arch_handle_exception(int e_vector, int signo, int err_code,
269 } 386 }
270 } 387 }
271 388
389 get_debugreg(dr6, 6);
390 if (!(dr6 & 0x4000)) {
391 int breakno;
392
393 for (breakno = 0; breakno < 4; breakno++) {
394 if (dr6 & (1 << breakno) &&
395 breakinfo[breakno].type == 0) {
396 /* Set restore flag: */
397 linux_regs->flags |= X86_EFLAGS_RF;
398 break;
399 }
400 }
401 }
402 set_debugreg(0UL, 6);
403 kgdb_correct_hw_break();
404
272 return 0; 405 return 0;
273 } 406 }
274 407
@@ -426,4 +559,9 @@ unsigned long kgdb_arch_pc(int exception, struct pt_regs *regs)
426struct kgdb_arch arch_kgdb_ops = { 559struct kgdb_arch arch_kgdb_ops = {
427 /* Breakpoint instruction: */ 560 /* Breakpoint instruction: */
428 .gdb_bpt_instr = { 0xcc }, 561 .gdb_bpt_instr = { 0xcc },
562 .flags = KGDB_HW_BREAKPOINT,
563 .set_hw_breakpoint = kgdb_set_hw_break,
564 .remove_hw_breakpoint = kgdb_remove_hw_break,
565 .remove_all_hw_break = kgdb_remove_all_hw_break,
566 .correct_hw_break = kgdb_correct_hw_break,
429}; 567};
diff --git a/arch/x86/kernel/setup64.c b/arch/x86/kernel/setup64.c
index e24c45677094..143aa78c566b 100644
--- a/arch/x86/kernel/setup64.c
+++ b/arch/x86/kernel/setup64.c
@@ -11,6 +11,7 @@
11#include <linux/bootmem.h> 11#include <linux/bootmem.h>
12#include <linux/bitops.h> 12#include <linux/bitops.h>
13#include <linux/module.h> 13#include <linux/module.h>
14#include <linux/kgdb.h>
14#include <asm/pda.h> 15#include <asm/pda.h>
15#include <asm/pgtable.h> 16#include <asm/pgtable.h>
16#include <asm/processor.h> 17#include <asm/processor.h>
@@ -327,6 +328,17 @@ void __cpuinit cpu_init (void)
327 load_TR_desc(); 328 load_TR_desc();
328 load_LDT(&init_mm.context); 329 load_LDT(&init_mm.context);
329 330
331#ifdef CONFIG_KGDB
332 /*
333 * If the kgdb is connected no debug regs should be altered. This
334 * is only applicable when KGDB and a KGDB I/O module are built
335 * into the kernel and you are using early debugging with
336 * kgdbwait. KGDB will control the kernel HW breakpoint registers.
337 */
338 if (kgdb_connected && arch_kgdb_ops.correct_hw_break)
339 arch_kgdb_ops.correct_hw_break();
340 else {
341#endif
330 /* 342 /*
331 * Clear all 6 debug registers: 343 * Clear all 6 debug registers:
332 */ 344 */
@@ -337,6 +349,10 @@ void __cpuinit cpu_init (void)
337 set_debugreg(0UL, 3); 349 set_debugreg(0UL, 3);
338 set_debugreg(0UL, 6); 350 set_debugreg(0UL, 6);
339 set_debugreg(0UL, 7); 351 set_debugreg(0UL, 7);
352#ifdef CONFIG_KGDB
353 /* If the kgdb is connected no debug regs should be altered. */
354 }
355#endif
340 356
341 fpu_init(); 357 fpu_init();
342 358
diff --git a/kernel/kgdb.c b/kernel/kgdb.c
index 319c08c92ee2..68aea78407e4 100644
--- a/kernel/kgdb.c
+++ b/kernel/kgdb.c
@@ -1139,10 +1139,10 @@ static void gdb_cmd_break(struct kgdb_state *ks)
1139 error = kgdb_remove_sw_break(addr); 1139 error = kgdb_remove_sw_break(addr);
1140 else if (remcom_in_buffer[0] == 'Z') 1140 else if (remcom_in_buffer[0] == 'Z')
1141 error = arch_kgdb_ops.set_hw_breakpoint(addr, 1141 error = arch_kgdb_ops.set_hw_breakpoint(addr,
1142 (int)length, *bpt_type); 1142 (int)length, *bpt_type - '0');
1143 else if (remcom_in_buffer[0] == 'z') 1143 else if (remcom_in_buffer[0] == 'z')
1144 error = arch_kgdb_ops.remove_hw_breakpoint(addr, 1144 error = arch_kgdb_ops.remove_hw_breakpoint(addr,
1145 (int) length, *bpt_type); 1145 (int) length, *bpt_type - '0');
1146 1146
1147 if (error == 0) 1147 if (error == 0)
1148 strcpy(remcom_out_buffer, "OK"); 1148 strcpy(remcom_out_buffer, "OK");