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