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