summaryrefslogtreecommitdiff
path: root/i386
diff options
context:
space:
mode:
authorSamuel Thibault <samuel.thibault@ens-lyon.org>2013-02-04 10:38:16 +0100
committerSamuel Thibault <samuel.thibault@ens-lyon.org>2013-02-04 10:38:16 +0100
commitd14e4924c55e3016d1ddf7a38a7e93460ca10ac8 (patch)
tree29da83f6fc7ed527484887b6ee5ce305fe8bb165 /i386
parent51e87d005139a435cd846ac5c224eed5042c4fa0 (diff)
Add x86 hardware debugging register support
This adds using the x86 hardware debugging registers, either from the kernel through db_set_hw_watchpoint, or from userland through i386_DEBUG_STATE. While the kernel is using the registers, the userland values are ignored. * i386/i386/db_interface.c (kernel_dr, ids): New variables. (db_load_context, db_get_debug_state, db_set_debug_state, db_dr, db_set_hw_watchpoint): New functions. (kdb_trap): Use get_dr* instead of dr_addr[]. * i386/i386/db_interface.h (db_user_to_kernel_address, db_set_hw_watchpoint) (db_dr, db_get_debug_state, db_set_debug_state, db_load_context): Add functions prototypes. (dr0, dr1, dr2, dr3): Remove functions prototypes. * i386/i386/locore.S (dr6, dr0, dr1, dr2, dr3): Remove functions. (dr_msk, dr_addr): Remove variables. * i386/include/mach/i386/thread_status.h (i386_DEBUG_STATE): Add macro. (i386_debug_state): Add structure. (i386_DEBUG_STATE_COUNT): Add macro. * i386/i386/thread.h: Include <mach/machine/thread_status.h>. (i386_machine_state): Add `struct i386_debug_state ids' field. * i386/i386/pcb.c: Include <i386/db_interface.h>. (switch_ktss): Call db_load_context. (thread_setstatus, thread_getstatus): Add I386_DEBUG_STATE case. * i386/i386/proc_reg.h (get_dr0, set_dr0, get_dr1, set_dr1, get_dr2, set_dr2, get_dr3, set_dr3, get_dr6, set_dr6, get_dr7, set_dr7): Add macros.
Diffstat (limited to 'i386')
-rw-r--r--i386/i386/db_interface.c150
-rw-r--r--i386/i386/db_interface.h31
-rw-r--r--i386/i386/locore.S122
-rw-r--r--i386/i386/pcb.c32
-rw-r--r--i386/i386/proc_reg.h126
-rw-r--r--i386/i386/thread.h2
-rw-r--r--i386/include/mach/i386/thread_status.h7
7 files changed, 336 insertions, 134 deletions
diff --git a/i386/i386/db_interface.c b/i386/i386/db_interface.c
index 66cc8b5..c6d7fb4 100644
--- a/i386/i386/db_interface.c
+++ b/i386/i386/db_interface.c
@@ -27,8 +27,6 @@
* Interface to new debugger.
*/
-#if MACH_KDB
-
#include <sys/reboot.h>
#include <vm/pmap.h>
@@ -58,12 +56,149 @@
#include <machine/db_interface.h>
#include <machine/machspl.h>
+#if MACH_KDB
+/* Whether the kernel uses any debugging register. */
+static int kernel_dr;
+#endif
+
+void db_load_context(pcb_t pcb)
+{
+#if MACH_KDB
+ int s = splhigh();
+
+ if (kernel_dr) {
+ splx(s);
+ return;
+ }
+#endif
+
+ /* Else set user debug registers */
+ set_dr0(pcb->ims.ids.dr[0]);
+ set_dr1(pcb->ims.ids.dr[1]);
+ set_dr2(pcb->ims.ids.dr[2]);
+ set_dr3(pcb->ims.ids.dr[3]);
+ set_dr7(pcb->ims.ids.dr[7]);
+#if MACH_KDB
+ splx(s);
+#endif
+}
+
+void db_get_debug_state(
+ pcb_t pcb,
+ struct i386_debug_state *state)
+{
+ *state = pcb->ims.ids;
+}
+
+kern_return_t db_set_debug_state(
+ pcb_t pcb,
+ const struct i386_debug_state *state)
+{
+ int i;
+
+ for (i = 0; i <= 3; i++)
+ if (state->dr[i] < VM_MIN_ADDRESS
+ || state->dr[i] >= VM_MAX_ADDRESS)
+ return KERN_INVALID_ARGUMENT;
+
+ pcb->ims.ids = *state;
+
+ if (pcb == current_thread()->pcb)
+ db_load_context(pcb);
+
+ return KERN_SUCCESS;
+}
+
+#if MACH_KDB
+
struct i386_saved_state *i386_last_saved_statep;
struct i386_saved_state i386_nested_saved_state;
unsigned i386_last_kdb_sp;
extern thread_t db_default_thread;
+static struct i386_debug_state ids;
+
+void db_dr (
+ int num,
+ vm_offset_t linear_addr,
+ int type,
+ int len,
+ int persistence)
+{
+ int s = splhigh();
+ unsigned long dr7;
+
+ if (!kernel_dr) {
+ if (!linear_addr) {
+ splx(s);
+ return;
+ }
+ kernel_dr = 1;
+ /* Clear user debugging registers */
+ set_dr7(0);
+ set_dr0(0);
+ set_dr1(0);
+ set_dr2(0);
+ set_dr3(0);
+ }
+
+ ids.dr[num] = linear_addr;
+ switch (num) {
+ case 0: set_dr0(linear_addr); break;
+ case 1: set_dr1(linear_addr); break;
+ case 2: set_dr2(linear_addr); break;
+ case 3: set_dr3(linear_addr); break;
+ }
+
+ /* Replace type/len/persistence for DRnum in dr7 */
+ dr7 = get_dr7 ();
+ dr7 &= ~(0xfUL << (4*num+16)) & ~(0x3UL << (2*num));
+ dr7 |= (((len << 2) | type) << (4*num+16)) | (persistence << (2*num));
+ set_dr7 (dr7);
+
+ if (kernel_dr) {
+ if (!ids.dr[0] && !ids.dr[1] && !ids.dr[2] && !ids.dr[3]) {
+ /* Not used any more, switch back to user debugging registers */
+ kernel_dr = 0;
+ db_load_context(current_thread()->pcb);
+ }
+ }
+ splx(s);
+}
+
+void db_set_hw_watchpoint(
+ int num,
+ task_t task,
+ db_addr_t addr,
+ vm_size_t size)
+{
+ unsigned int kern_addr;
+
+ if (size != 1 && size != 2 && size != 4)
+ return;
+
+ if (addr & (size-1))
+ /* Unaligned */
+ return;
+
+ if (!addr) {
+ db_dr (num, 0, 0, 0, 0);
+ db_printf("Hardware watchpoint %d deleted\n", num);
+ }
+
+ if (task) {
+ if (db_user_to_kernel_address(task, addr, &kern_addr, 1) < 0)
+ return;
+ addr = kern_addr;
+ }
+ addr = kvtolin(addr);
+
+ db_dr (num, addr, I386_DB_TYPE_W, size-1, I386_DB_LOCAL|I386_DB_GLOBAL);
+
+ db_printf("Hardware watchpoint %d set for %x\n", num, addr);
+}
+
/*
* Print trap reason.
*/
@@ -96,15 +231,14 @@ kdb_trap(
switch (type) {
case T_DEBUG: /* single_step */
{
- extern int dr_addr[];
int addr;
- int status = dr6();
+ int status = get_dr6();
if (status & 0xf) { /* hmm hdw break */
- addr = status & 0x8 ? dr_addr[3] :
- status & 0x4 ? dr_addr[2] :
- status & 0x2 ? dr_addr[1] :
- dr_addr[0];
+ addr = status & 0x8 ? get_dr3() :
+ status & 0x4 ? get_dr2() :
+ status & 0x2 ? get_dr1() :
+ get_dr0();
regs->efl |= EFL_RF;
db_single_step_cmd(addr, 0, 1, "p");
}
diff --git a/i386/i386/db_interface.h b/i386/i386/db_interface.h
index 10a02e2..a8dfdce 100644
--- a/i386/i386/db_interface.h
+++ b/i386/i386/db_interface.h
@@ -53,6 +53,12 @@ extern boolean_t db_phys_eq (
task_t task2,
vm_offset_t addr2);
+extern int db_user_to_kernel_address(
+ task_t task,
+ vm_offset_t addr,
+ unsigned int *kaddr,
+ int flag);
+
extern void db_task_name (task_t task);
#define I386_DB_TYPE_X 0
@@ -67,9 +73,26 @@ extern void db_task_name (task_t task);
#define I386_DB_LOCAL 1
#define I386_DB_GLOBAL 2
-extern unsigned long dr0 (vm_offset_t linear_addr, int type, int len, int persistence);
-extern unsigned long dr1 (vm_offset_t linear_addr, int type, int len, int persistence);
-extern unsigned long dr2 (vm_offset_t linear_addr, int type, int len, int persistence);
-extern unsigned long dr3 (vm_offset_t linear_addr, int type, int len, int persistence);
+extern void db_set_hw_watchpoint(
+ int num,
+ task_t task,
+ vm_offset_t addr,
+ vm_size_t size);
+
+extern void db_dr (
+ int num,
+ vm_offset_t linear_addr,
+ int type,
+ int len,
+ int persistence);
+
+extern void db_get_debug_state(
+ pcb_t pcb,
+ struct i386_debug_state *state);
+extern kern_return_t db_set_debug_state(
+ pcb_t pcb,
+ const struct i386_debug_state *state);
+
+extern void db_load_context(pcb_t pcb);
#endif /* _I386_DB_INTERFACE_H_ */
diff --git a/i386/i386/locore.S b/i386/i386/locore.S
index 9aa8485..e1befa7 100644
--- a/i386/i386/locore.S
+++ b/i386/i386/locore.S
@@ -1411,128 +1411,6 @@ _inst_fetch_fault:
-ENTRY(dr6)
-#ifdef MACH_RING1
- pushl %ebx
- movl $6, %ebx
- call __hyp_get_debugreg
- popl %ebx
-#else /* MACH_RING1 */
- movl %db6, %eax
-#endif /* MACH_RING1 */
- ret
-
-/* dr<i>(address, type, len, persistence)
- */
-ENTRY(dr0)
- movl S_ARG0, %eax
- movl %eax,EXT(dr_addr)
-#ifdef MACH_RING1
- pushl %ebx
- movl $0,%ebx
- movl %eax,%ecx
- call __hyp_set_debugreg
-#else /* MACH_RING1 */
- movl %eax, %db0
-#endif /* MACH_RING1 */
- movl $0, %ecx
- jmp 0f
-ENTRY(dr1)
- movl S_ARG0, %eax
- movl %eax,EXT(dr_addr)+1*4
-#ifdef MACH_RING1
- pushl %ebx
- movl $1,%ebx
- movl %eax,%ecx
- call __hyp_set_debugreg
-#else /* MACH_RING1 */
- movl %eax, %db1
-#endif /* MACH_RING1 */
- movl $2, %ecx
- jmp 0f
-ENTRY(dr2)
- movl S_ARG0, %eax
- movl %eax,EXT(dr_addr)+2*4
-#ifdef MACH_RING1
- pushl %ebx
- movl $2,%ebx
- movl %eax,%ecx
- call __hyp_set_debugreg
-#else /* MACH_RING1 */
- movl %eax, %db2
-#endif /* MACH_RING1 */
- movl $4, %ecx
- jmp 0f
-
-ENTRY(dr3)
- movl S_ARG0, %eax
- movl %eax,EXT(dr_addr)+3*4
-#ifdef MACH_RING1
- pushl %ebx
- movl $3,%ebx
- movl %eax,%ecx
- call __hyp_set_debugreg
-#else /* MACH_RING1 */
- movl %eax, %db3
-#endif /* MACH_RING1 */
- movl $6, %ecx
-
-0:
- pushl %ebp
- movl %esp, %ebp
-
-#ifdef MACH_RING1
- movl $7,%ebx
- call __hyp_get_debugreg
- movl %eax, %edx
-#else /* MACH_RING1 */
- movl %db7, %edx
-#endif /* MACH_RING1 */
- movl %edx,EXT(dr_addr)+4*4
- andl dr_msk(,%ecx,2),%edx /* clear out new entry */
- movl %edx,EXT(dr_addr)+5*4
- movzbl B_ARG3, %eax
- andb $3, %al
- shll %cl, %eax
- orl %eax, %edx
-
- movzbl B_ARG1, %eax
- andb $3, %al
- addb %cl, %cl
- addb $0x10, %cl
- shll %cl, %eax
- orl %eax, %edx
-
- movzbl B_ARG2, %eax
- andb $3, %al
- addb $0x2, %cl
- shll %cl, %eax
- orl %eax, %edx
-
-#ifdef MACH_RING1
- movl $7,%ebx
- movl %edx, %ecx
- call __hyp_set_debugreg
- popl %ebx
-#else /* MACH_RING1 */
- movl %edx, %db7
-#endif /* MACH_RING1 */
- movl %edx,EXT(dr_addr)+7*4
- movl %edx, %eax
- leave
- ret
-
- .data
-dr_msk:
- .long ~0x000f0003
- .long ~0x00f0000c
- .long ~0x0f000030
- .long ~0xf00000c0
-ENTRY(dr_addr)
- .long 0,0,0,0
- .long 0,0,0,0
- .text
-
/*
* cpu_shutdown()
* Force reboot
diff --git a/i386/i386/pcb.c b/i386/i386/pcb.c
index 7d632a6..97ec00e 100644
--- a/i386/i386/pcb.c
+++ b/i386/i386/pcb.c
@@ -46,6 +46,7 @@
#include <i386/proc_reg.h>
#include <i386/seg.h>
#include <i386/user_ldt.h>
+#include <i386/db_interface.h>
#include <i386/fpu.h>
#include "eflags.h"
#include "gdt.h"
@@ -215,6 +216,8 @@ void switch_ktss(pcb)
pcb->ims.user_gdt, sizeof pcb->ims.user_gdt);
#endif /* MACH_PV_DESCRIPTORS */
+ db_load_context(pcb);
+
/*
* Load the floating-point context, if necessary.
*/
@@ -621,6 +624,21 @@ kern_return_t thread_setstatus(thread, flavor, tstate, count)
break;
}
+ case i386_DEBUG_STATE:
+ {
+ register struct i386_debug_state *state;
+ kern_return_t ret;
+
+ if (count < i386_DEBUG_STATE_COUNT)
+ return KERN_INVALID_ARGUMENT;
+
+ state = (struct i386_debug_state *) tstate;
+ ret = db_set_debug_state(thread->pcb, state);
+ if (ret)
+ return ret;
+ break;
+ }
+
default:
return(KERN_INVALID_ARGUMENT);
}
@@ -760,6 +778,20 @@ kern_return_t thread_getstatus(thread, flavor, tstate, count)
break;
}
+ case i386_DEBUG_STATE:
+ {
+ register struct i386_debug_state *state;
+
+ if (*count < i386_DEBUG_STATE_COUNT)
+ return KERN_INVALID_ARGUMENT;
+
+ state = (struct i386_debug_state *) tstate;
+ db_get_debug_state(thread->pcb, state);
+
+ *count = i386_DEBUG_STATE_COUNT;
+ break;
+ }
+
default:
return(KERN_INVALID_ARGUMENT);
}
diff --git a/i386/i386/proc_reg.h b/i386/i386/proc_reg.h
index f1b2c89..8083536 100644
--- a/i386/i386/proc_reg.h
+++ b/i386/i386/proc_reg.h
@@ -234,6 +234,132 @@ extern unsigned long cr3;
asm("jmp 0f\n" \
"0:\n")
+#ifdef MACH_RING1
+#define get_dr0() hyp_get_debugreg(0)
+#else
+#define get_dr0() \
+ ({ \
+ register unsigned long _temp__; \
+ asm volatile("movl %%dr0, %0" : "=r" (_temp__)); \
+ _temp__; \
+ })
+#endif
+
+#ifdef MACH_RING1
+#define set_dr0(value) hyp_set_debugreg(0, value)
+#else
+#define set_dr0(value) \
+ ({ \
+ register unsigned long _temp__ = (value); \
+ asm volatile("movl %0,%%dr0" : : "r" (_temp__)); \
+ })
+#endif
+
+#ifdef MACH_RING1
+#define get_dr1() hyp_get_debugreg(1)
+#else
+#define get_dr1() \
+ ({ \
+ register unsigned long _temp__; \
+ asm volatile("movl %%dr1, %0" : "=r" (_temp__)); \
+ _temp__; \
+ })
+#endif
+
+#ifdef MACH_RING1
+#define set_dr1(value) hyp_set_debugreg(1, value)
+#else
+#define set_dr1(value) \
+ ({ \
+ register unsigned long _temp__ = (value); \
+ asm volatile("movl %0,%%dr1" : : "r" (_temp__)); \
+ })
+#endif
+
+#ifdef MACH_RING1
+#define get_dr2() hyp_get_debugreg(2)
+#else
+#define get_dr2() \
+ ({ \
+ register unsigned long _temp__; \
+ asm volatile("movl %%dr2, %0" : "=r" (_temp__)); \
+ _temp__; \
+ })
+#endif
+
+#ifdef MACH_RING1
+#define set_dr2(value) hyp_set_debugreg(2, value)
+#else
+#define set_dr2(value) \
+ ({ \
+ register unsigned long _temp__ = (value); \
+ asm volatile("movl %0,%%dr2" : : "r" (_temp__)); \
+ })
+#endif
+
+#ifdef MACH_RING1
+#define get_dr3() hyp_get_debugreg(3)
+#else
+#define get_dr3() \
+ ({ \
+ register unsigned long _temp__; \
+ asm volatile("movl %%dr3, %0" : "=r" (_temp__)); \
+ _temp__; \
+ })
+#endif
+
+#ifdef MACH_RING1
+#define set_dr3(value) hyp_set_debugreg(3, value)
+#else
+#define set_dr3(value) \
+ ({ \
+ register unsigned long _temp__ = (value); \
+ asm volatile("movl %0,%%dr3" : : "r" (_temp__)); \
+ })
+#endif
+
+#ifdef MACH_RING1
+#define get_dr6() hyp_get_debugreg(6)
+#else
+#define get_dr6() \
+ ({ \
+ register unsigned long _temp__; \
+ asm volatile("movl %%dr6, %0" : "=r" (_temp__)); \
+ _temp__; \
+ })
+#endif
+
+#ifdef MACH_RING1
+#define set_dr6(value) hyp_set_debugreg(6, value)
+#else
+#define set_dr6(value) \
+ ({ \
+ register unsigned long _temp__ = (value); \
+ asm volatile("movl %0,%%dr6" : : "r" (_temp__)); \
+ })
+#endif
+
+#ifdef MACH_RING1
+#define get_dr7() hyp_get_debugreg(7)
+#else
+#define get_dr7() \
+ ({ \
+ register unsigned long _temp__; \
+ asm volatile("movl %%dr7, %0" : "=r" (_temp__)); \
+ _temp__; \
+ })
+#endif
+
+#ifdef MACH_RING1
+#define set_dr7(value) hyp_set_debugreg(7, value)
+#else
+#define set_dr7(value) \
+ ({ \
+ register unsigned long _temp__ = (value); \
+ asm volatile("movl %0,%%dr7" : : "r" (_temp__)); \
+ })
+#endif
+
#endif /* __GNUC__ */
#endif /* __ASSEMBLER__ */
diff --git a/i386/i386/thread.h b/i386/i386/thread.h
index eddd25c..450ec55 100644
--- a/i386/i386/thread.h
+++ b/i386/i386/thread.h
@@ -36,6 +36,7 @@
#include <mach/boolean.h>
#include <mach/machine/vm_types.h>
#include <mach/machine/fp_reg.h>
+#include <mach/machine/thread_status.h>
#include <kern/lock.h>
@@ -168,6 +169,7 @@ struct i386_machine_state {
struct i386_fpsave_state *ifps;
struct v86_assist_state v86s;
struct real_descriptor user_gdt[USER_GDT_SLOTS];
+ struct i386_debug_state ids;
};
typedef struct pcb {
diff --git a/i386/include/mach/i386/thread_status.h b/i386/include/mach/i386/thread_status.h
index 5f20355..ba1e3de 100644
--- a/i386/include/mach/i386/thread_status.h
+++ b/i386/include/mach/i386/thread_status.h
@@ -56,6 +56,7 @@
#define i386_ISA_PORT_MAP_STATE 3
#define i386_V86_ASSIST_STATE 4
#define i386_REGS_SEGS_STATE 5
+#define i386_DEBUG_STATE 6
/*
* This structure is used for both
@@ -144,4 +145,10 @@ struct v86_interrupt_table {
#define i386_V86_ASSIST_STATE_COUNT \
(sizeof(struct i386_v86_assist_state)/sizeof(unsigned int))
+struct i386_debug_state {
+ unsigned int dr[8];
+};
+#define i386_DEBUG_STATE_COUNT \
+ (sizeof(struct i386_debug_state)/sizeof(unsigned int))
+
#endif /* _MACH_I386_THREAD_STATUS_H_ */