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
dbreg_set_watchreg(int watchnum,vm_offset_t watchaddr,vm_size_t size,int access,struct dbreg * d)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
dbreg_clr_watchreg(int watchnum,struct dbreg * d)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
dbreg_sync(struct dbreg * dp)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
dbreg_set_watchpoint(vm_offset_t addr,vm_size_t size,int access)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
dbreg_clr_watchpoint(vm_offset_t addr,vm_size_t size)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 *
watchtype_str(int type)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
dbreg_list_watchpoints(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
amd64_db_resume_dbreg(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
kdb_cpu_set_watchpoint(vm_offset_t addr,vm_size_t size,int access)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
kdb_cpu_clr_watchpoint(vm_offset_t addr,vm_size_t size)299 kdb_cpu_clr_watchpoint(vm_offset_t addr, vm_size_t size)
300 {
301
302 return (dbreg_clr_watchpoint(addr, size));
303 }
304