/*- * Mach Operating System * Copyright (c) 1991,1990 Carnegie Mellon University * All Rights Reserved. * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the * software, derivative works or modified versions, and any portions * thereof, and that both notices appear in supporting documentation. * * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. * * Carnegie Mellon requests users of this software to return to * * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU * School of Computer Science * Carnegie Mellon University * Pittsburgh PA 15213-3890 * * any improvements or extensions that they make and grant Carnegie the * rights to redistribute these changes. */ #include "opt_ddb.h" #include #include #include #include #include #include #include #include #include #include #include #define NDBREGS 4 #ifdef __amd64__ #define MAXWATCHSIZE 8 #else #define MAXWATCHSIZE 4 #endif /* * Set a watchpoint in the debug register denoted by 'watchnum'. */ static void dbreg_set_watchreg(int watchnum, vm_offset_t watchaddr, vm_size_t size, int access, struct dbreg *d) { int len; MPASS(watchnum >= 0 && watchnum < NDBREGS); /* size must be 1 for an execution breakpoint */ if (access == DBREG_DR7_EXEC) size = 1; /* * we can watch a 1, 2, or 4 byte sized location */ switch (size) { case 1: len = DBREG_DR7_LEN_1; break; case 2: len = DBREG_DR7_LEN_2; break; case 4: len = DBREG_DR7_LEN_4; break; #if MAXWATCHSIZE >= 8 case 8: len = DBREG_DR7_LEN_8; break; #endif default: return; } /* clear the bits we are about to affect */ d->dr[7] &= ~DBREG_DR7_MASK(watchnum); /* set drN register to the address, N=watchnum */ DBREG_DRX(d, watchnum) = watchaddr; /* enable the watchpoint */ d->dr[7] |= DBREG_DR7_SET(watchnum, len, access, DBREG_DR7_GLOBAL_ENABLE); } /* * Remove a watchpoint from the debug register denoted by 'watchnum'. */ static void dbreg_clr_watchreg(int watchnum, struct dbreg *d) { MPASS(watchnum >= 0 && watchnum < NDBREGS); d->dr[7] &= ~DBREG_DR7_MASK(watchnum); DBREG_DRX(d, watchnum) = 0; } /* * Sync the debug registers. Other cores will read these values from the PCPU * area when they resume. See amd64_db_resume_dbreg() below. */ static void dbreg_sync(struct dbreg *dp) { #ifdef __amd64__ struct pcpu *pc; int cpu, c; cpu = PCPU_GET(cpuid); CPU_FOREACH(c) { if (c == cpu) continue; pc = pcpu_find(c); memcpy(pc->pc_dbreg, dp, sizeof(*dp)); pc->pc_dbreg_cmd = PC_DBREG_CMD_LOAD; } #endif } int dbreg_set_watchpoint(vm_offset_t addr, vm_size_t size, int access) { struct dbreg *d; int avail, i, wsize; #ifdef __amd64__ d = (struct dbreg *)PCPU_PTR(dbreg); #else /* debug registers aren't stored in PCPU on i386. */ struct dbreg d_temp; d = &d_temp; #endif /* Validate the access type */ if (access != DBREG_DR7_EXEC && access != DBREG_DR7_WRONLY && access != DBREG_DR7_RDWR) return (EINVAL); fill_dbregs(NULL, d); /* * Check if there are enough available registers to cover the desired * area. */ avail = 0; for (i = 0; i < NDBREGS; i++) { if (!DBREG_DR7_ENABLED(d->dr[7], i)) avail++; } if (avail * MAXWATCHSIZE < size) return (EBUSY); for (i = 0; i < NDBREGS && size > 0; i++) { if (!DBREG_DR7_ENABLED(d->dr[7], i)) { if ((size >= 8 || (avail == 1 && size > 4)) && MAXWATCHSIZE == 8) wsize = 8; else if (size > 2) wsize = 4; else wsize = size; dbreg_set_watchreg(i, addr, wsize, access, d); addr += wsize; size -= wsize; avail--; } } set_dbregs(NULL, d); dbreg_sync(d); return (0); } int dbreg_clr_watchpoint(vm_offset_t addr, vm_size_t size) { struct dbreg *d; int i; #ifdef __amd64__ d = (struct dbreg *)PCPU_PTR(dbreg); #else /* debug registers aren't stored in PCPU on i386. */ struct dbreg d_temp; d = &d_temp; #endif fill_dbregs(NULL, d); for (i = 0; i < NDBREGS; i++) { if (DBREG_DR7_ENABLED(d->dr[7], i)) { if (DBREG_DRX((d), i) >= addr && DBREG_DRX((d), i) < addr + size) dbreg_clr_watchreg(i, d); } } set_dbregs(NULL, d); dbreg_sync(d); return (0); } #ifdef DDB static const char * watchtype_str(int type) { switch (type) { case DBREG_DR7_EXEC: return ("execute"); case DBREG_DR7_RDWR: return ("read/write"); case DBREG_DR7_WRONLY: return ("write"); default: return ("invalid"); } } void dbreg_list_watchpoints(void) { struct dbreg d; int i, len, type; fill_dbregs(NULL, &d); db_printf("\nhardware watchpoints:\n"); db_printf(" watch status type len address\n"); db_printf(" ----- -------- ---------- --- ----------\n"); for (i = 0; i < NDBREGS; i++) { if (DBREG_DR7_ENABLED(d.dr[7], i)) { type = DBREG_DR7_ACCESS(d.dr[7], i); len = DBREG_DR7_LEN(d.dr[7], i); db_printf(" %-5d %-8s %10s %3d ", i, "enabled", watchtype_str(type), len + 1); db_printsym((db_addr_t)DBREG_DRX(&d, i), DB_STGY_ANY); db_printf("\n"); } else { db_printf(" %-5d disabled\n", i); } } } #endif #ifdef __amd64__ /* Sync debug registers when resuming from debugger. */ void amd64_db_resume_dbreg(void) { struct dbreg *d; switch (PCPU_GET(dbreg_cmd)) { case PC_DBREG_CMD_LOAD: d = (struct dbreg *)PCPU_PTR(dbreg); set_dbregs(NULL, d); PCPU_SET(dbreg_cmd, PC_DBREG_CMD_NONE); break; } } #endif int kdb_cpu_set_watchpoint(vm_offset_t addr, vm_size_t size, int access) { /* Convert the KDB access type */ switch (access) { case KDB_DBG_ACCESS_W: access = DBREG_DR7_WRONLY; break; case KDB_DBG_ACCESS_RW: access = DBREG_DR7_RDWR; break; case KDB_DBG_ACCESS_R: /* FALLTHROUGH: read-only not supported */ default: return (EINVAL); } return (dbreg_set_watchpoint(addr, size, access)); } int kdb_cpu_clr_watchpoint(vm_offset_t addr, vm_size_t size) { return (dbreg_clr_watchpoint(addr, size)); }