/* $NetBSD: dbregs.c,v 1.14 2019/01/14 18:54:07 maxv Exp $ */ /* * Copyright (c) 2016 The NetBSD Foundation, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include struct pool x86_dbregspl; static struct dbreg initdbstate; #define X86_BREAKPOINT_CONDITION_DETECTED ( \ X86_DR6_DR0_BREAKPOINT_CONDITION_DETECTED | \ X86_DR6_DR1_BREAKPOINT_CONDITION_DETECTED | \ X86_DR6_DR2_BREAKPOINT_CONDITION_DETECTED | \ X86_DR6_DR3_BREAKPOINT_CONDITION_DETECTED ) #define X86_GLOBAL_BREAKPOINT ( \ X86_DR7_GLOBAL_DR0_BREAKPOINT | \ X86_DR7_GLOBAL_DR1_BREAKPOINT | \ X86_DR7_GLOBAL_DR2_BREAKPOINT | \ X86_DR7_GLOBAL_DR3_BREAKPOINT ) void x86_dbregs_init(void) { /* DR0-DR3 should always be 0 */ initdbstate.dr[0] = rdr0(); initdbstate.dr[1] = rdr1(); initdbstate.dr[2] = rdr2(); initdbstate.dr[3] = rdr3(); /* DR4-DR5 are reserved - skip */ /* DR6 and DR7 contain predefined nonzero bits */ initdbstate.dr[6] = rdr6(); initdbstate.dr[7] = rdr7(); /* DR8-DR15 are reserved - skip */ /* * Explicitly reset some bits just in case they could be * set by brave software/hardware before the kernel boot. */ initdbstate.dr[6] &= ~X86_BREAKPOINT_CONDITION_DETECTED; initdbstate.dr[7] &= ~X86_DR7_GENERAL_DETECT_ENABLE; pool_init(&x86_dbregspl, sizeof(struct dbreg), 16, 0, 0, "dbregs", NULL, IPL_NONE); } static void x86_dbregs_reset(void) { /* * It's sufficient to just disable Debug Control Register (DR7). * It will deactivate hardware watchpoints. */ ldr7(0); /* * However at some point we need to clear Debug Status Registers * (DR6). The CPU will never do it automatically. * * Clear BREAKPOINT_CONDITION_DETECTED bits and ignore the rest. */ ldr6(rdr6() & ~X86_BREAKPOINT_CONDITION_DETECTED); } void x86_dbregs_clear(struct lwp *l) { struct pcb *pcb = lwp_getpcb(l); struct dbreg *dbregs; KASSERT(l == curlwp); if (__predict_true(pcb->pcb_dbregs == NULL)) { KASSERT((pcb->pcb_flags & PCB_DBREGS) == 0); return; } dbregs = pcb->pcb_dbregs; kpreempt_disable(); pcb->pcb_dbregs = NULL; pcb->pcb_flags &= ~PCB_DBREGS; x86_dbregs_reset(); kpreempt_enable(); pool_put(&x86_dbregspl, dbregs); } void x86_dbregs_abandon(struct lwp *l) { struct pcb *pcb = lwp_getpcb(l); kpreempt_disable(); pcb->pcb_flags &= ~PCB_DBREGS; x86_dbregs_reset(); kpreempt_enable(); } void x86_dbregs_read(struct lwp *l, struct dbreg *regs) { struct pcb *pcb = lwp_getpcb(l); if (pcb->pcb_dbregs == NULL) { pcb->pcb_dbregs = pool_get(&x86_dbregspl, PR_WAITOK); memcpy(pcb->pcb_dbregs, &initdbstate, sizeof(initdbstate)); pcb->pcb_flags |= PCB_DBREGS; } memcpy(regs, pcb->pcb_dbregs, sizeof(*regs)); } void x86_dbregs_save(struct lwp *l) { struct pcb *pcb = lwp_getpcb(l); if (!(pcb->pcb_flags & PCB_DBREGS)) { return; } KASSERT(pcb->pcb_dbregs != NULL); pcb->pcb_dbregs->dr[0] = rdr0(); pcb->pcb_dbregs->dr[1] = rdr1(); pcb->pcb_dbregs->dr[2] = rdr2(); pcb->pcb_dbregs->dr[3] = rdr3(); pcb->pcb_dbregs->dr[6] = rdr6(); pcb->pcb_dbregs->dr[7] = rdr7(); } void x86_dbregs_restore(struct lwp *l) { struct pcb *pcb = lwp_getpcb(l); if (!(pcb->pcb_flags & PCB_DBREGS)) { return; } KASSERT(pcb->pcb_dbregs != NULL); ldr0(pcb->pcb_dbregs->dr[0]); ldr1(pcb->pcb_dbregs->dr[1]); ldr2(pcb->pcb_dbregs->dr[2]); ldr3(pcb->pcb_dbregs->dr[3]); ldr6(pcb->pcb_dbregs->dr[6]); ldr7(pcb->pcb_dbregs->dr[7]); } void x86_dbregs_store_dr6(struct lwp *l) { struct pcb *pcb = lwp_getpcb(l); KASSERT(l == curlwp); KASSERT(pcb->pcb_dbregs != NULL); pcb->pcb_dbregs->dr[6] = rdr6(); } int x86_dbregs_user_trap(void) { register_t dr7, dr6; register_t bp; dr7 = rdr7(); if ((dr7 & X86_GLOBAL_BREAKPOINT) == 0) { /* * All Global Breakpoint bits are zero, thus the trap couldn't * have been caused by the hardware debug registers. */ return 0; } dr6 = rdr6(); bp = dr6 & X86_BREAKPOINT_CONDITION_DETECTED; if (!bp) { /* * None of the breakpoint bits are set, meaning this * trap was not caused by any of the debug registers. */ return 0; } /* * At least one of the breakpoints was hit, check to see * which ones and if any of them are user space addresses. */ if (bp & X86_DR6_DR0_BREAKPOINT_CONDITION_DETECTED) if (rdr0() < (vaddr_t)VM_MAXUSER_ADDRESS) return 1; if (bp & X86_DR6_DR1_BREAKPOINT_CONDITION_DETECTED) if (rdr1() < (vaddr_t)VM_MAXUSER_ADDRESS) return 1; if (bp & X86_DR6_DR2_BREAKPOINT_CONDITION_DETECTED) if (rdr2() < (vaddr_t)VM_MAXUSER_ADDRESS) return 1; if (bp & X86_DR6_DR3_BREAKPOINT_CONDITION_DETECTED) if (rdr3() < (vaddr_t)VM_MAXUSER_ADDRESS) return 1; return 0; } int x86_dbregs_validate(const struct dbreg *regs) { size_t i; /* Check that DR0-DR3 contain user-space address */ for (i = 0; i < X86_DBREGS; i++) { if (regs->dr[i] >= (vaddr_t)VM_MAXUSER_ADDRESS) return EINVAL; } #ifndef i386 if (regs->dr[6] & X86_DR6_MBZ) { return EINVAL; } if (regs->dr[7] & X86_DR7_MBZ) { return EINVAL; } #endif if (regs->dr[7] & X86_DR7_GENERAL_DETECT_ENABLE) { return EINVAL; } /* * Skip checks for reserved registers (DR4-DR5, DR8-DR15). */ return 0; } void x86_dbregs_write(struct lwp *l, const struct dbreg *regs) { struct pcb *pcb = lwp_getpcb(l); if (pcb->pcb_dbregs == NULL) { pcb->pcb_dbregs = pool_get(&x86_dbregspl, PR_WAITOK); } memcpy(pcb->pcb_dbregs, regs, sizeof(*regs)); pcb->pcb_flags |= PCB_DBREGS; } /* * Called with preemption disabled. */ void x86_dbregs_switch(struct lwp *oldlwp, struct lwp *newlwp) { struct pcb *oldpcb, *newpcb; bool olddb, newdb; oldpcb = (oldlwp != NULL) ? lwp_getpcb(oldlwp) : NULL; newpcb = lwp_getpcb(newlwp); olddb = false; if (oldpcb) { olddb = (oldpcb->pcb_flags & PCB_DBREGS) != 0; } newdb = (newpcb->pcb_flags & PCB_DBREGS) != 0; if (__predict_true(!olddb && !newdb)) { /* fast path */ return; } if (olddb) { x86_dbregs_save(oldlwp); } if (newdb) { x86_dbregs_restore(newlwp); } else if (olddb) { x86_dbregs_reset(); } }