PERFORCE change 43758 for review

Peter Wemm peter at FreeBSD.org
Wed Dec 10 13:17:57 PST 2003


http://perforce.freebsd.org/chv.cgi?CH=43758

Change 43758 by peter at peter_daintree on 2003/12/10 13:17:34

	Checkpoint WIP addition of hardware debug register support.
	Notable todo:
	1) make it compile
	2) deal with the 16 vs 8 debug registers
	3) finish the cpu switch code

Affected files ...

.. //depot/projects/hammer/sys/amd64/amd64/cpu_switch.S#15 edit
.. //depot/projects/hammer/sys/amd64/amd64/db_trace.c#13 edit
.. //depot/projects/hammer/sys/amd64/amd64/genassym.c#29 edit
.. //depot/projects/hammer/sys/amd64/amd64/machdep.c#76 edit
.. //depot/projects/hammer/sys/amd64/amd64/trap.c#36 edit
.. //depot/projects/hammer/sys/amd64/amd64/vm_machdep.c#24 edit
.. //depot/projects/hammer/sys/amd64/include/cpufunc.h#17 edit
.. //depot/projects/hammer/sys/amd64/include/pcb.h#14 edit

Differences ...

==== //depot/projects/hammer/sys/amd64/amd64/cpu_switch.S#15 (text+ko) ====

@@ -130,6 +130,27 @@
 	movl	%fs,PCB_FS(%r8)
 	movl	%gs,PCB_GS(%r8)
 
+#if 0
+	/* Test if debug registers should be saved. */
+	testl	$PCB_DBREGS,PCB_FLAGS(%edx)
+	jz      1f                              /* no, skip over */
+	movl    %dr7,%eax                       /* yes, do the save */
+	movl    %eax,PCB_DR7(%edx)
+	andl    $0x0000fc00, %eax               /* disable all watchpoints */
+	movl    %eax,%dr7
+	movl    %dr6,%eax
+	movl    %eax,PCB_DR6(%edx)
+	movl    %dr3,%eax
+	movl    %eax,PCB_DR3(%edx)
+	movl    %dr2,%eax
+	movl    %eax,PCB_DR2(%edx)
+	movl    %dr1,%eax
+	movl    %eax,PCB_DR1(%edx)
+	movl    %dr0,%eax
+	movl    %eax,PCB_DR0(%edx)
+1:
+#endif
+
 	/* have we used fp, and need a save? */
 	cmpq	%rdi,PCPU(FPCURTHREAD)
 	jne	1f
@@ -223,6 +244,30 @@
 	movq	%r8, PCPU(CURPCB)
 	movq	%rsi, PCPU(CURTHREAD)		/* into next thread */
 
+#if 0
+	/*
+	 * Restore debug registers.  The special code for dr7 is to
+	 * preserve the current values of its reserved bits.
+	 */
+	movl    PCB_DR6(%edx),%eax
+	movl    %eax,%dr6
+	movl    PCB_DR3(%edx),%eax
+	movl    %eax,%dr3
+	movl    PCB_DR2(%edx),%eax
+	movl    %eax,%dr2
+	movl    PCB_DR1(%edx),%eax
+	movl    %eax,%dr1
+	movl    PCB_DR0(%edx),%eax
+	movl    %eax,%dr0
+	movl	%dr7,%eax
+	andl    $0x0000fc00,%eax
+	movl    PCB_DR7(%edx),%ecx
+	andl	$~0x0000fc00,%ecx
+	orl     %ecx,%eax
+	movl    %eax,%dr7
+1:
+#endif
+
 	ret
 
 #ifdef INVARIANTS

==== //depot/projects/hammer/sys/amd64/amd64/db_trace.c#13 (text+ko) ====

@@ -46,7 +46,6 @@
 #include <ddb/db_sym.h>
 #include <ddb/db_variables.h>
 
-#if 0
 db_varfcn_t db_dr0;
 db_varfcn_t db_dr1;
 db_varfcn_t db_dr2;
@@ -55,7 +54,6 @@
 db_varfcn_t db_dr5;
 db_varfcn_t db_dr6;
 db_varfcn_t db_dr7;
-#endif
 
 /*
  * Machine register set.
@@ -87,7 +85,6 @@
 	{ "r15",	&ddb_regs.tf_r15,    FCN_NULL },
 	{ "rip",	&ddb_regs.tf_rip,    FCN_NULL },
 	{ "rflags",	&ddb_regs.tf_rflags, FCN_NULL },
-#if 0
 	{ "dr0",	NULL,		     db_dr0 },
 	{ "dr1",	NULL,		     db_dr1 },
 	{ "dr2",	NULL,		     db_dr2 },
@@ -96,7 +93,6 @@
 	{ "dr5",	NULL,		     db_dr5 },
 	{ "dr6",	NULL,		     db_dr6 },
 	{ "dr7",	NULL,		     db_dr7 },
-#endif
 };
 struct db_variable *db_eregs = db_regs + sizeof(db_regs)/sizeof(db_regs[0]);
 
@@ -124,12 +120,10 @@
 		struct proc *p, struct amd64_frame *frame, db_addr_t callpc);
 
 
-#if 0
 static char * watchtype_str(int type);
 int  amd64_set_watch(int watchnum, unsigned int watchaddr, int size, int access,
 		    struct dbreg * d);
 int  amd64_clr_watch(int watchnum, struct dbreg * d);
-#endif
 int  db_md_set_watchpoint(db_expr_t addr, db_expr_t size);
 int  db_md_clr_watchpoint(db_expr_t addr, db_expr_t size);
 void db_md_list_watchpoints(void);
@@ -517,7 +511,6 @@
 	db_stack_trace_cmd(ebp, 1, -1, NULL);
 }
 
-#if 0
 #define DB_DRX_FUNC(reg)		\
 int					\
 db_ ## reg (vp, valuep, op)		\
@@ -724,26 +717,3 @@
 	}
 	db_printf("\n");
 }
-
-#else
-int
-db_md_set_watchpoint(addr, size)
-	db_expr_t addr;
-	db_expr_t size;
-{
-	return (-1);
-}
-
-int
-db_md_clr_watchpoint(addr, size)
-	db_expr_t addr;
-	db_expr_t size;
-{
-	return (-1);
-}
-
-void
-db_md_list_watchpoints()
-{
-}
-#endif

==== //depot/projects/hammer/sys/amd64/amd64/genassym.c#29 (text+ko) ====

@@ -135,6 +135,13 @@
 ASSYM(PCB_ES, offsetof(struct pcb, pcb_es));
 ASSYM(PCB_FS, offsetof(struct pcb, pcb_fs));
 ASSYM(PCB_GS, offsetof(struct pcb, pcb_gs));
+ASSYM(PCB_DR0, offsetof(struct pcb, pcb_dr0));
+ASSYM(PCB_DR1, offsetof(struct pcb, pcb_dr1));
+ASSYM(PCB_DR2, offsetof(struct pcb, pcb_dr2));
+ASSYM(PCB_DR3, offsetof(struct pcb, pcb_dr3));
+ASSYM(PCB_DR6, offsetof(struct pcb, pcb_dr6));
+ASSYM(PCB_DR7, offsetof(struct pcb, pcb_dr7));
+ASSYM(PCB_DBREGS, PCB_DBREGS);
 
 ASSYM(PCB_FLAGS, offsetof(struct pcb, pcb_flags));
 ASSYM(PCB_FULLCTX, PCB_FULLCTX);

==== //depot/projects/hammer/sys/amd64/amd64/machdep.c#76 (text+ko) ====

@@ -534,6 +534,28 @@
 	regs->tf_cs = _ucodesel;
 
 	/*
+	 * Reset the hardware debug registers if they were in use.
+	 * They won't have any meaning for the newly exec'd process.
+	 */
+	if (pcb->pcb_flags & PCB_DBREGS) {
+		pcb->pcb_dr0 = 0;
+		pcb->pcb_dr1 = 0;
+		pcb->pcb_dr2 = 0;
+		pcb->pcb_dr3 = 0;
+		pcb->pcb_dr6 = 0;
+		pcb->pcb_dr7 = 0;
+		if (pcb == PCPU_GET(curpcb)) {
+			/*
+			 * Clear the debug registers on the running
+			 * CPU, otherwise they will end up affecting
+			 * the next process we switch to.
+			 */
+			reset_dbregs();
+		}
+		pcb->pcb_flags &= ~PCB_DBREGS;
+	}
+
+	/*
 	 * Arrange to trap the next fpu or `fwait' instruction (see fpu.c
 	 * for why fwait must be trapped at least if there is an fpu or an
 	 * emulator).  This is mainly to handle the case where npx0 is not
@@ -1559,17 +1581,181 @@
 int
 fill_dbregs(struct thread *td, struct dbreg *dbregs)
 {
+	struct pcb *pcb;
 
+	if (td == NULL) {
+		dbregs->dr[0] = rdr0();
+		dbregs->dr[1] = rdr1();
+		dbregs->dr[2] = rdr2();
+		dbregs->dr[3] = rdr3();
+		dbregs->dr[4] = rdr4();
+		dbregs->dr[5] = rdr5();
+		dbregs->dr[6] = rdr6();
+		dbregs->dr[7] = rdr7();
+	} else {
+		pcb = td->td_pcb;
+		dbregs->dr[0] = pcb->pcb_dr0;
+		dbregs->dr[1] = pcb->pcb_dr1;
+		dbregs->dr[2] = pcb->pcb_dr2;
+		dbregs->dr[3] = pcb->pcb_dr3;
+		dbregs->dr[4] = 0;
+		dbregs->dr[5] = 0;
+		dbregs->dr[6] = pcb->pcb_dr6;
+		dbregs->dr[7] = pcb->pcb_dr7;
+	}
 	return (0);
 }
 
 int
 set_dbregs(struct thread *td, struct dbreg *dbregs)
 {
+	struct pcb *pcb;
+	int i;
+	u_int64_t mask1, mask2;
+
+	if (td == NULL) {
+		load_dr0(dbregs->dr[0]);
+		load_dr1(dbregs->dr[1]);
+		load_dr2(dbregs->dr[2]);
+		load_dr3(dbregs->dr[3]);
+		load_dr4(dbregs->dr[4]);
+		load_dr5(dbregs->dr[5]);
+		load_dr6(dbregs->dr[6]);
+		load_dr7(dbregs->dr[7]);
+	} else {
+		/*
+		 * Don't let an illegal value for dr7 get set.  Specifically,
+		 * check for undefined settings.  Setting these bit patterns
+		 * result in undefined behaviour and can lead to an unexpected
+		 * TRCTRAP or a general protection fault right here.
+		 */
+		for (i = 0, mask1 = 0x3<<16, mask2 = 0x2<<16; i < 8;
+		     i++, mask1 <<= 2, mask2 <<= 2)
+			if ((dbregs->dr[7] & mask1) == mask2)
+				return (EINVAL);
+
+		pcb = td->td_pcb;
+
+		/*
+		 * Don't let a process set a breakpoint that is not within the
+		 * process's address space.  If a process could do this, it
+		 * could halt the system by setting a breakpoint in the kernel
+		 * (if ddb was enabled).  Thus, we need to check to make sure
+		 * that no breakpoints are being enabled for addresses outside
+		 * process's address space, unless, perhaps, we were called by
+		 * uid 0.
+		 *
+		 * XXX - what about when the watched area of the user's
+		 * address space is written into from within the kernel
+		 * ... wouldn't that still cause a breakpoint to be generated
+		 * from within kernel mode?
+		 */
+
+		if (suser(td) != 0) {
+			if (dbregs->dr[7] & 0x3) {
+				/* dr0 is enabled */
+				if (dbregs->dr[0] >= VM_MAXUSER_ADDRESS)
+					return (EINVAL);
+			}
+			if (dbregs->dr[7] & 0x3<<2) {
+				/* dr1 is enabled */
+				if (dbregs->dr[1] >= VM_MAXUSER_ADDRESS)
+					return (EINVAL);
+			}
+			if (dbregs->dr[7] & 0x3<<4) {
+				/* dr2 is enabled */
+				if (dbregs->dr[2] >= VM_MAXUSER_ADDRESS)
+					return (EINVAL);
+			}
+			if (dbregs->dr[7] & 0x3<<6) {
+				/* dr3 is enabled */
+				if (dbregs->dr[3] >= VM_MAXUSER_ADDRESS)
+					return (EINVAL);
+			}
+		}
+
+		pcb->pcb_dr0 = dbregs->dr[0];
+		pcb->pcb_dr1 = dbregs->dr[1];
+		pcb->pcb_dr2 = dbregs->dr[2];
+		pcb->pcb_dr3 = dbregs->dr[3];
+		pcb->pcb_dr6 = dbregs->dr[6];
+		pcb->pcb_dr7 = dbregs->dr[7];
+
+		pcb->pcb_flags |= PCB_DBREGS;
+	}
 
 	return (0);
 }
 
+/*
+ * Return > 0 if a hardware breakpoint has been hit, and the
+ * breakpoint was in user space.  Return 0, otherwise.
+ */
+int
+user_dbreg_trap(void)
+{
+        u_int64_t dr7, dr6; /* debug registers dr6 and dr7 */
+        u_int64_t bp;       /* breakpoint bits extracted from dr6 */
+        int nbp;            /* number of breakpoints that triggered */
+        caddr_t addr[4];    /* breakpoint addresses */
+        int i;
+        
+        dr7 = rdr7();
+        if ((dr7 & 0x000000ff) == 0) {
+                /*
+                 * all GE and LE bits in the dr7 register are zero,
+                 * thus the trap couldn't have been caused by the
+                 * hardware debug registers
+                 */
+                return 0;
+        }
+
+        nbp = 0;
+        dr6 = rdr6();
+        bp = dr6 & 0x0000000f;
+
+        if (!bp) {
+                /*
+                 * None of the breakpoint bits are set meaning this
+                 * trap was not caused by any of the debug registers
+                 */
+                return 0;
+        }
+
+        /*
+         * at least one of the breakpoints were hit, check to see
+         * which ones and if any of them are user space addresses
+         */
+
+        if (bp & 0x01) {
+                addr[nbp++] = (caddr_t)rdr0();
+        }
+        if (bp & 0x02) {
+                addr[nbp++] = (caddr_t)rdr1();
+        }
+        if (bp & 0x04) {
+                addr[nbp++] = (caddr_t)rdr2();
+        }
+        if (bp & 0x08) {
+                addr[nbp++] = (caddr_t)rdr3();
+        }
+
+        for (i=0; i<nbp; i++) {
+                if (addr[i] <
+                    (caddr_t)VM_MAXUSER_ADDRESS) {
+                        /*
+                         * addr[i] is in user space
+                         */
+                        return nbp;
+                }
+        }
+
+        /*
+         * None of the breakpoints are in user space.
+         */
+        return 0;
+}
+
 #ifndef DDB
 void
 Debugger(const char *msg)

==== //depot/projects/hammer/sys/amd64/amd64/trap.c#36 (text+ko) ====

@@ -397,6 +397,24 @@
 
 		case T_TRCTRAP:	 /* trace trap */
 			/*
+			 * Ignore debug register trace traps due to
+			 * accesses in the user's address space, which
+			 * can happen under several conditions such as
+			 * if a user sets a watchpoint on a buffer and
+			 * then passes that buffer to a system call.
+			 * We still want to get TRCTRAPS for addresses
+			 * in kernel space because that is useful when
+			 * debugging the kernel.
+			 */
+			if (user_dbreg_trap()) {
+				/*
+				 * Reset breakpoint bits because the
+				 * processor doesn't
+				 */
+				load_dr6(rdr6() & 0xfffffff0);
+				goto out;
+			}
+			/*
 			 * FALLTHROUGH (TRCTRAP kernel mode, kernel address)
 			 */
 		case T_BPTFLT:

==== //depot/projects/hammer/sys/amd64/amd64/vm_machdep.c#24 (text+ko) ====

@@ -162,6 +162,7 @@
 	pcb2->pcb_rip = (register_t)fork_trampoline;
 	pcb2->pcb_rflags = td2->td_frame->tf_rflags & ~PSL_I; /* ints disabled */
 	/*-
+	 * pcb2->pcb_dr*:	cloned above.
 	 * pcb2->pcb_savefpu:	cloned above.
 	 * pcb2->pcb_flags:	cloned above.
 	 * pcb2->pcb_onfault:	cloned above (always NULL here?).
@@ -202,9 +203,12 @@
 void
 cpu_exit(struct thread *td)
 {
-	struct mdproc *mdp;
 
-	mdp = &td->td_proc->p_md;
+	if (pcb->pcb_flags & PCB_DBREGS) {
+		/* disable all hardware breakpoints */
+		reset_dbregs();
+		pcb->pcb_flags &= ~PCB_DBREGS;
+	}
 }
 
 void
@@ -213,6 +217,11 @@
 
 	if (td == PCPU_GET(fpcurthread))
 		fpudrop();
+	if (pcb->pcb_flags & PCB_DBREGS) {
+		/* disable all hardware breakpoints */
+		reset_dbregs();
+		pcb->pcb_flags &= ~PCB_DBREGS;
+	}
 }
 
 void
@@ -296,6 +305,7 @@
 	pcb2->pcb_rflags = PSL_KERNEL; /* ints disabled */
 	/*
 	 * If we didn't copy the pcb, we'd need to do the following registers:
+	 * pcb2->pcb_dr*:	cloned above.
 	 * pcb2->pcb_savefpu:	cloned above.
 	 * pcb2->pcb_rflags:	cloned above.
 	 * pcb2->pcb_onfault:	cloned above (always NULL here?).

==== //depot/projects/hammer/sys/amd64/include/cpufunc.h#17 (text+ko) ====

@@ -584,6 +584,118 @@
 	__asm __volatile("ltr %0" : : "r" (sel));
 }
 
+static __inline u_int64_t
+rdr0(void)
+{
+	u_int64_t data;
+	__asm __volatile("movq %%dr0,%0" : "=r" (data));
+	return (data);
+}
+
+static __inline void
+load_dr0(u_int64_t dr0)
+{
+	__asm __volatile("movq %0,%%dr0" : : "r" (dr0));
+}
+
+static __inline u_int64_t
+rdr1(void)
+{
+	u_int64_t data;
+	__asm __volatile("movq %%dr1,%0" : "=r" (data));
+	return (data);
+}
+
+static __inline void
+load_dr1(u_int64_t dr1)
+{
+	__asm __volatile("movq %0,%%dr1" : : "r" (dr1));
+}
+
+static __inline u_int64_t
+rdr2(void)
+{
+	u_int64_t data;
+	__asm __volatile("movq %%dr2,%0" : "=r" (data));
+	return (data);
+}
+
+static __inline void
+load_dr2(u_int64_t dr2)
+{
+	__asm __volatile("movq %0,%%dr2" : : "r" (dr2));
+}
+
+static __inline u_int64_t
+rdr3(void)
+{
+	u_int64_t data;
+	__asm __volatile("movq %%dr3,%0" : "=r" (data));
+	return (data);
+}
+
+static __inline void
+load_dr3(u_int64_t dr3)
+{
+	__asm __volatile("movq %0,%%dr3" : : "r" (dr3));
+}
+
+static __inline u_int64_t
+rdr4(void)
+{
+	u_int64_t data;
+	__asm __volatile("movq %%dr4,%0" : "=r" (data));
+	return (data);
+}
+
+static __inline void
+load_dr4(u_int64_t dr4)
+{
+	__asm __volatile("movq %0,%%dr4" : : "r" (dr4));
+}
+
+static __inline u_int64_t
+rdr5(void)
+{
+	u_int64_t data;
+	__asm __volatile("movq %%dr5,%0" : "=r" (data));
+	return (data);
+}
+
+static __inline void
+load_dr5(u_int64_t dr5)
+{
+	__asm __volatile("movq %0,%%dr5" : : "r" (dr5));
+}
+
+static __inline u_int64_t
+rdr6(void)
+{
+	u_int64_t data;
+	__asm __volatile("movq %%dr6,%0" : "=r" (data));
+	return (data);
+}
+
+static __inline void
+load_dr6(u_int64_t dr6)
+{
+	__asm __volatile("movq %0,%%dr6" : : "r" (dr6));
+}
+
+static __inline u_int64_t
+rdr7(void)
+{
+	u_int64_t data;
+	__asm __volatile("movq %%dr7,%0" : "=r" (data));
+	return (data);
+}
+
+static __inline void
+load_dr7(u_int64_t dr7)
+{
+	__asm __volatile("movq %0,%%dr7" : : "r" (dr7));
+}
+
 static __inline register_t
 intr_disable(void)
 {
@@ -650,13 +762,28 @@
 void	wbinvd(void);
 void	write_rflags(u_int rf);
 void	wrmsr(u_int msr, u_int64_t newval);
-void	load_dr7(u_int dr7);
+u_int64_t	rdr0(void);
+void	load_dr0(u_int64_t dr0);
+u_int64_t	rdr1(void);
+void	load_dr1(u_int64_t dr1);
+u_int64_t	rdr2(void);
+void	load_dr2(u_int64_t dr2);
+u_int64_t	rdr3(void);
+void	load_dr3(u_int64_t dr3);
+u_int64_t	rdr4(void);
+void	load_dr4(u_int64_t dr4);
+u_int64_t	rdr5(void);
+void	load_dr5(u_int64_t dr5);
+u_int64_t	rdr6(void);
+void	load_dr6(u_int64_t dr6);
+u_int64_t	rdr7(void);
+void	load_dr7(u_int64_t dr7);
 register_t	intr_disable(void);
 void	intr_restore(register_t rf);
 
 #endif	/* __GNUC__ */
 
-void    reset_dbregs(void);
+void	reset_dbregs(void);
 
 __END_DECLS
 

==== //depot/projects/hammer/sys/amd64/include/pcb.h#14 (text+ko) ====

@@ -64,11 +64,18 @@
 	u_int32_t	pcb_es;
 	u_int32_t	pcb_fs;
 	u_int32_t	pcb_gs;
+	u_int64_t	pcb_dr0;
+	u_int64_t	pcb_dr1;
+	u_int64_t	pcb_dr2;
+	u_int64_t	pcb_dr3;
+	u_int64_t	pcb_dr6;
+	u_int64_t	pcb_dr7;
 
 	struct	savefpu	pcb_save;
 	u_long	pcb_flags;
-#define	PCB_FPUINITDONE	0x01	/* fpu state is initialized */
-#define	PCB_FULLCTX	0x02	/* full context restore on sysret */
+#define	PCB_DBREGS	0x02	/* process using debug registers */
+#define	PCB_FPUINITDONE	0x08	/* fpu state is initialized */
+#define	PCB_FULLCTX	0x80	/* full context restore on sysret */
 
 	caddr_t	pcb_onfault;	/* copyin/out fault recovery */
 };


More information about the p4-projects mailing list