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