/* * Copyright (C) 2008 Stefan Hajnoczi . * Copyright (C) 2016 Michael Brown . * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * You can also choose to distribute this program under the terms of * the Unmodified Binary Distribution Licence (as given in the file * COPYING.UBDL), provided that you have satisfied its requirements. */ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include #include #include #include #include #include /** @file * * GDB architecture-specific bits for x86 * */ /** Number of hardware breakpoints */ #define NUM_HWBP 4 /** Debug register 7: Global breakpoint enable */ #define DR7_G( bp ) ( 2 << ( 2 * (bp) ) ) /** Debug register 7: Global exact breakpoint enable */ #define DR7_GE ( 1 << 9 ) /** Debug register 7: Break on data writes */ #define DR7_RWLEN_WRITE 0x11110000 /** Debug register 7: Break on data access */ #define DR7_RWLEN_ACCESS 0x33330000 /** Debug register 7: One-byte length */ #define DR7_RWLEN_1 0x00000000 /** Debug register 7: Two-byte length */ #define DR7_RWLEN_2 0x44440000 /** Debug register 7: Four-byte length */ #define DR7_RWLEN_4 0xcccc0000 /** Debug register 7: Eight-byte length */ #define DR7_RWLEN_8 0x88880000 /** Debug register 7: Breakpoint R/W and length mask */ #define DR7_RWLEN_MASK( bp ) ( 0xf0000 << ( 4 * (bp) ) ) /** Hardware breakpoint addresses (debug registers 0-3) */ static unsigned long dr[NUM_HWBP]; /** Active value of debug register 7 */ static unsigned long dr7 = DR7_GE; /** * Update debug registers * */ static void gdbmach_update ( void ) { /* Set debug registers */ __asm__ __volatile__ ( "mov %0, %%dr0" : : "r" ( dr[0] ) ); __asm__ __volatile__ ( "mov %0, %%dr1" : : "r" ( dr[1] ) ); __asm__ __volatile__ ( "mov %0, %%dr2" : : "r" ( dr[2] ) ); __asm__ __volatile__ ( "mov %0, %%dr3" : : "r" ( dr[3] ) ); __asm__ __volatile__ ( "mov %0, %%dr7" : : "r" ( dr7 ) ); } /** * Find reusable or available hardware breakpoint * * @v addr Linear address * @v rwlen Control bits * @ret bp Hardware breakpoint, or negative error */ static int gdbmach_find ( unsigned long addr, unsigned int rwlen ) { unsigned int i; int bp = -ENOENT; /* Look for a reusable or available breakpoint */ for ( i = 0 ; i < NUM_HWBP ; i++ ) { /* If breakpoint is not enabled, then it is available */ if ( ! ( dr7 & DR7_G ( i ) ) ) { bp = i; continue; } /* If breakpoint is enabled and has the same address * and control bits, then reuse it. */ if ( ( dr[i] == addr ) && ( ( ( dr7 ^ rwlen ) & DR7_RWLEN_MASK ( i ) ) == 0 ) ) { bp = i; break; } } return bp; } /** * Set hardware breakpoint * * @v type GDB breakpoint type * @v addr Virtual address * @v len Length * @v enable Enable (not disable) breakpoint * @ret rc Return status code */ int gdbmach_set_breakpoint ( int type, unsigned long addr, size_t len, int enable ) { unsigned int rwlen; unsigned long mask; int bp; /* Parse breakpoint type */ switch ( type ) { case GDBMACH_WATCH: rwlen = DR7_RWLEN_WRITE; break; case GDBMACH_AWATCH: rwlen = DR7_RWLEN_ACCESS; break; default: return -ENOTSUP; } /* Parse breakpoint length */ switch ( len ) { case 1: rwlen |= DR7_RWLEN_1; break; case 2: rwlen |= DR7_RWLEN_2; break; case 4: rwlen |= DR7_RWLEN_4; break; case 8: rwlen |= DR7_RWLEN_8; break; default: return -ENOTSUP; } /* Convert to linear address */ if ( sizeof ( physaddr_t ) <= sizeof ( uint32_t ) ) addr = virt_to_phys ( ( void * ) addr ); /* Find reusable or available hardware breakpoint */ bp = gdbmach_find ( addr, rwlen ); if ( bp < 0 ) return ( enable ? -ENOBUFS : 0 ); /* Configure this breakpoint */ DBGC ( &dr[0], "GDB bp %d at %p+%zx type %d (%sabled)\n", bp, ( ( void * ) addr ), len, type, ( enable ? "en" : "dis" ) ); dr[bp] = addr; mask = DR7_RWLEN_MASK ( bp ); dr7 = ( ( dr7 & ~mask ) | ( rwlen & mask ) ); mask = DR7_G ( bp ); dr7 &= ~mask; if ( enable ) dr7 |= mask; /* Update debug registers */ gdbmach_update(); return 0; } /** * Handle exception * * @v signo GDB signal number * @v regs Register dump */ __asmcall void gdbmach_handler ( int signo, gdbreg_t *regs ) { unsigned long dr7_disabled = DR7_GE; unsigned long dr6_clear = 0; /* Temporarily disable breakpoints */ __asm__ __volatile__ ( "mov %0, %%dr7\n" : : "r" ( dr7_disabled ) ); /* Handle exception */ DBGC ( &dr[0], "GDB signal %d\n", signo ); DBGC2_HDA ( &dr[0], 0, regs, ( GDBMACH_NREGS * sizeof ( *regs ) ) ); gdbstub_handler ( signo, regs ); DBGC ( &dr[0], "GDB signal %d returning\n", signo ); DBGC2_HDA ( &dr[0], 0, regs, ( GDBMACH_NREGS * sizeof ( *regs ) ) ); /* Clear breakpoint status register */ __asm__ __volatile__ ( "mov %0, %%dr6\n" : : "r" ( dr6_clear ) ); /* Re-enable breakpoints */ __asm__ __volatile__ ( "mov %0, %%dr7\n" : : "r" ( dr7 ) ); } /** * CPU exception vectors * * Note that we cannot intercept anything from INT8 (double fault) * upwards, since these overlap by default with IRQ0-7. */ static void * gdbmach_vectors[] = { gdbmach_sigfpe, /* Divide by zero */ gdbmach_sigtrap, /* Debug trap */ NULL, /* Non-maskable interrupt */ gdbmach_sigtrap, /* Breakpoint */ gdbmach_sigstkflt, /* Overflow */ gdbmach_sigstkflt, /* Bound range exceeded */ gdbmach_sigill, /* Invalid opcode */ }; /** * Initialise GDB */ void gdbmach_init ( void ) { unsigned int i; /* Hook CPU exception vectors */ for ( i = 0 ; i < ( sizeof ( gdbmach_vectors ) / sizeof ( gdbmach_vectors[0] ) ) ; i++ ) { if ( gdbmach_vectors[i] ) set_interrupt_vector ( i, gdbmach_vectors[i] ); } }