]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/x86/x86/dbreg.c
stack(9): Disable KASAN in stack_capture()
[FreeBSD/FreeBSD.git] / sys / x86 / x86 / dbreg.c
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 }