xref: /freebsd/sys/x86/x86/dbreg.c (revision 16038816)
1 /*-
2  * Mach Operating System
3  * Copyright (c) 1991,1990 Carnegie Mellon University
4  * All Rights Reserved.
5  *
6  * Permission to use, copy, modify and distribute this software and its
7  * documentation is hereby granted, provided that both the copyright
8  * notice and this permission notice appear in all copies of the
9  * software, derivative works or modified versions, and any portions
10  * thereof, and that both notices appear in supporting documentation.
11  *
12  * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS
13  * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
14  * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
15  *
16  * Carnegie Mellon requests users of this software to return to
17  *
18  *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
19  *  School of Computer Science
20  *  Carnegie Mellon University
21  *  Pittsburgh PA 15213-3890
22  *
23  * any improvements or extensions that they make and grant Carnegie the
24  * rights to redistribute these changes.
25  */
26 
27 #include "opt_ddb.h"
28 
29 #include <sys/types.h>
30 #include <sys/kdb.h>
31 #include <sys/pcpu.h>
32 #include <sys/smp.h>
33 #include <sys/systm.h>
34 
35 #include <machine/frame.h>
36 #include <machine/kdb.h>
37 #include <machine/md_var.h>
38 
39 #include <ddb/ddb.h>
40 #include <ddb/db_sym.h>
41 
42 #define NDBREGS		4
43 #ifdef __amd64__
44 #define	MAXWATCHSIZE	8
45 #else
46 #define	MAXWATCHSIZE	4
47 #endif
48 
49 /*
50  * Set a watchpoint in the debug register denoted by 'watchnum'.
51  */
52 static void
53 dbreg_set_watchreg(int watchnum, vm_offset_t watchaddr, vm_size_t size,
54     int access, struct dbreg *d)
55 {
56 	int len;
57 
58 	MPASS(watchnum >= 0 && watchnum < NDBREGS);
59 
60 	/* size must be 1 for an execution breakpoint */
61 	if (access == DBREG_DR7_EXEC)
62 		size = 1;
63 
64 	/*
65 	 * we can watch a 1, 2, or 4 byte sized location
66 	 */
67 	switch (size) {
68 	case 1:
69 		len = DBREG_DR7_LEN_1;
70 		break;
71 	case 2:
72 		len = DBREG_DR7_LEN_2;
73 		break;
74 	case 4:
75 		len = DBREG_DR7_LEN_4;
76 		break;
77 #if MAXWATCHSIZE >= 8
78 	case 8:
79 		len = DBREG_DR7_LEN_8;
80 		break;
81 #endif
82 	default:
83 		return;
84 	}
85 
86 	/* clear the bits we are about to affect */
87 	d->dr[7] &= ~DBREG_DR7_MASK(watchnum);
88 
89 	/* set drN register to the address, N=watchnum */
90 	DBREG_DRX(d, watchnum) = watchaddr;
91 
92 	/* enable the watchpoint */
93 	d->dr[7] |= DBREG_DR7_SET(watchnum, len, access,
94 	    DBREG_DR7_GLOBAL_ENABLE);
95 }
96 
97 /*
98  * Remove a watchpoint from the debug register denoted by 'watchnum'.
99  */
100 static void
101 dbreg_clr_watchreg(int watchnum, struct dbreg *d)
102 {
103 	MPASS(watchnum >= 0 && watchnum < NDBREGS);
104 
105 	d->dr[7] &= ~DBREG_DR7_MASK(watchnum);
106 	DBREG_DRX(d, watchnum) = 0;
107 }
108 
109 /*
110  * Sync the debug registers. Other cores will read these values from the PCPU
111  * area when they resume. See amd64_db_resume_dbreg() below.
112  */
113 static void
114 dbreg_sync(struct dbreg *dp)
115 {
116 #ifdef __amd64__
117 	struct pcpu *pc;
118 	int cpu, c;
119 
120 	cpu = PCPU_GET(cpuid);
121 	CPU_FOREACH(c) {
122 		if (c == cpu)
123 			continue;
124 		pc = pcpu_find(c);
125 		memcpy(pc->pc_dbreg, dp, sizeof(*dp));
126 		pc->pc_dbreg_cmd = PC_DBREG_CMD_LOAD;
127 	}
128 #endif
129 }
130 
131 int
132 dbreg_set_watchpoint(vm_offset_t addr, vm_size_t size, int access)
133 {
134 	struct dbreg *d;
135 	int avail, i, wsize;
136 
137 #ifdef __amd64__
138 	d = (struct dbreg *)PCPU_PTR(dbreg);
139 #else
140 	/* debug registers aren't stored in PCPU on i386. */
141 	struct dbreg d_temp;
142 	d = &d_temp;
143 #endif
144 
145 	/* Validate the access type */
146 	if (access != DBREG_DR7_EXEC && access != DBREG_DR7_WRONLY &&
147 	    access != DBREG_DR7_RDWR)
148 		return (EINVAL);
149 
150 	fill_dbregs(NULL, d);
151 
152 	/*
153 	 * Check if there are enough available registers to cover the desired
154 	 * area.
155 	 */
156 	avail = 0;
157 	for (i = 0; i < NDBREGS; i++) {
158 		if (!DBREG_DR7_ENABLED(d->dr[7], i))
159 			avail++;
160 	}
161 
162 	if (avail * MAXWATCHSIZE < size)
163 		return (EBUSY);
164 
165 	for (i = 0; i < NDBREGS && size > 0; i++) {
166 		if (!DBREG_DR7_ENABLED(d->dr[7], i)) {
167 			if ((size >= 8 || (avail == 1 && size > 4)) &&
168 			    MAXWATCHSIZE == 8)
169 				wsize = 8;
170 			else if (size > 2)
171 				wsize = 4;
172 			else
173 				wsize = size;
174 			dbreg_set_watchreg(i, addr, wsize, access, d);
175 			addr += wsize;
176 			size -= wsize;
177 			avail--;
178 		}
179 	}
180 
181 	set_dbregs(NULL, d);
182 	dbreg_sync(d);
183 
184 	return (0);
185 }
186 
187 int
188 dbreg_clr_watchpoint(vm_offset_t addr, vm_size_t size)
189 {
190 	struct dbreg *d;
191 	int i;
192 
193 #ifdef __amd64__
194 	d = (struct dbreg *)PCPU_PTR(dbreg);
195 #else
196 	/* debug registers aren't stored in PCPU on i386. */
197 	struct dbreg d_temp;
198 	d = &d_temp;
199 #endif
200 	fill_dbregs(NULL, d);
201 
202 	for (i = 0; i < NDBREGS; i++) {
203 		if (DBREG_DR7_ENABLED(d->dr[7], i)) {
204 			if (DBREG_DRX((d), i) >= addr &&
205 			    DBREG_DRX((d), i) < addr + size)
206 				dbreg_clr_watchreg(i, d);
207 		}
208 	}
209 
210 	set_dbregs(NULL, d);
211 	dbreg_sync(d);
212 
213 	return (0);
214 }
215 
216 #ifdef DDB
217 static const char *
218 watchtype_str(int type)
219 {
220 
221 	switch (type) {
222 	case DBREG_DR7_EXEC:
223 		return ("execute");
224 	case DBREG_DR7_RDWR:
225 		return ("read/write");
226 	case DBREG_DR7_WRONLY:
227 		return ("write");
228 	default:
229 		return ("invalid");
230 	}
231 }
232 
233 void
234 dbreg_list_watchpoints(void)
235 {
236 	struct dbreg d;
237 	int i, len, type;
238 
239 	fill_dbregs(NULL, &d);
240 
241 	db_printf("\nhardware watchpoints:\n");
242 	db_printf("  watch    status        type  len     address\n");
243 	db_printf("  -----  --------  ----------  ---  ----------\n");
244 	for (i = 0; i < NDBREGS; i++) {
245 		if (DBREG_DR7_ENABLED(d.dr[7], i)) {
246 			type = DBREG_DR7_ACCESS(d.dr[7], i);
247 			len = DBREG_DR7_LEN(d.dr[7], i);
248 			db_printf("  %-5d  %-8s  %10s  %3d  ",
249 			    i, "enabled", watchtype_str(type), len + 1);
250 			db_printsym((db_addr_t)DBREG_DRX(&d, i), DB_STGY_ANY);
251 			db_printf("\n");
252 		} else {
253 			db_printf("  %-5d  disabled\n", i);
254 		}
255 	}
256 }
257 #endif
258 
259 #ifdef __amd64__
260 /* Sync debug registers when resuming from debugger. */
261 void
262 amd64_db_resume_dbreg(void)
263 {
264 	struct dbreg *d;
265 
266 	switch (PCPU_GET(dbreg_cmd)) {
267 	case PC_DBREG_CMD_LOAD:
268 		d = (struct dbreg *)PCPU_PTR(dbreg);
269 		set_dbregs(NULL, d);
270 		PCPU_SET(dbreg_cmd, PC_DBREG_CMD_NONE);
271 		break;
272 	}
273 }
274 #endif
275 
276 int
277 kdb_cpu_set_watchpoint(vm_offset_t addr, vm_size_t size, int access)
278 {
279 
280 	/* Convert the KDB access type */
281 	switch (access) {
282 	case KDB_DBG_ACCESS_W:
283 		access = DBREG_DR7_WRONLY;
284 		break;
285 	case KDB_DBG_ACCESS_RW:
286 		access = DBREG_DR7_RDWR;
287 		break;
288 	case KDB_DBG_ACCESS_R:
289 		/* FALLTHROUGH: read-only not supported */
290 	default:
291 		return (EINVAL);
292 	}
293 
294 	return (dbreg_set_watchpoint(addr, size, access));
295 }
296 
297 int
298 kdb_cpu_clr_watchpoint(vm_offset_t addr, vm_size_t size)
299 {
300 
301 	return (dbreg_clr_watchpoint(addr, size));
302 }
303