iPXE
gdbmach.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2008 Stefan Hajnoczi <stefanha@gmail.com>.
00003  * Copyright (C) 2016 Michael Brown <mbrown@fensystems.co.uk>.
00004  *
00005  * This program is free software; you can redistribute it and/or
00006  * modify it under the terms of the GNU General Public License as
00007  * published by the Free Software Foundation; either version 2 of the
00008  * License, or any later version.
00009  *
00010  * This program is distributed in the hope that it will be useful, but
00011  * WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013  * General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU General Public License
00016  * along with this program; if not, write to the Free Software
00017  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00018  * 02110-1301, USA.
00019  *
00020  * You can also choose to distribute this program under the terms of
00021  * the Unmodified Binary Distribution Licence (as given in the file
00022  * COPYING.UBDL), provided that you have satisfied its requirements.
00023  */
00024 
00025 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
00026 
00027 #include <stddef.h>
00028 #include <stdio.h>
00029 #include <errno.h>
00030 #include <assert.h>
00031 #include <ipxe/uaccess.h>
00032 #include <ipxe/gdbstub.h>
00033 #include <librm.h>
00034 #include <gdbmach.h>
00035 
00036 /** @file
00037  *
00038  * GDB architecture-specific bits for x86
00039  *
00040  */
00041 
00042 /** Number of hardware breakpoints */
00043 #define NUM_HWBP 4
00044 
00045 /** Debug register 7: Global breakpoint enable */
00046 #define DR7_G( bp ) ( 2 << ( 2 * (bp) ) )
00047 
00048 /** Debug register 7: Global exact breakpoint enable */
00049 #define DR7_GE ( 1 << 9 )
00050 
00051 /** Debug register 7: Break on data writes */
00052 #define DR7_RWLEN_WRITE 0x11110000
00053 
00054 /** Debug register 7: Break on data access */
00055 #define DR7_RWLEN_ACCESS 0x33330000
00056 
00057 /** Debug register 7: One-byte length */
00058 #define DR7_RWLEN_1 0x00000000
00059 
00060 /** Debug register 7: Two-byte length */
00061 #define DR7_RWLEN_2 0x44440000
00062 
00063 /** Debug register 7: Four-byte length */
00064 #define DR7_RWLEN_4 0xcccc0000
00065 
00066 /** Debug register 7: Eight-byte length */
00067 #define DR7_RWLEN_8 0x88880000
00068 
00069 /** Debug register 7: Breakpoint R/W and length mask */
00070 #define DR7_RWLEN_MASK( bp ) ( 0xf0000 << ( 4 * (bp) ) )
00071 
00072 /** Hardware breakpoint addresses (debug registers 0-3) */
00073 static unsigned long dr[NUM_HWBP];
00074 
00075 /** Active value of debug register 7 */
00076 static unsigned long dr7 = DR7_GE;
00077 
00078 /**
00079  * Update debug registers
00080  *
00081  */
00082 static void gdbmach_update ( void ) {
00083 
00084         /* Set debug registers */
00085         __asm__ __volatile__ ( "mov %0, %%dr0" : : "r" ( dr[0] ) );
00086         __asm__ __volatile__ ( "mov %0, %%dr1" : : "r" ( dr[1] ) );
00087         __asm__ __volatile__ ( "mov %0, %%dr2" : : "r" ( dr[2] ) );
00088         __asm__ __volatile__ ( "mov %0, %%dr3" : : "r" ( dr[3] ) );
00089         __asm__ __volatile__ ( "mov %0, %%dr7" : : "r" ( dr7 ) );
00090 }
00091 
00092 /**
00093  * Find reusable or available hardware breakpoint
00094  *
00095  * @v addr              Linear address
00096  * @v rwlen             Control bits
00097  * @ret bp              Hardware breakpoint, or negative error
00098  */
00099 static int gdbmach_find ( unsigned long addr, unsigned int rwlen ) {
00100         unsigned int i;
00101         int bp = -ENOENT;
00102 
00103         /* Look for a reusable or available breakpoint */
00104         for ( i = 0 ; i < NUM_HWBP ; i++ ) {
00105 
00106                 /* If breakpoint is not enabled, then it is available */
00107                 if ( ! ( dr7 & DR7_G ( i ) ) ) {
00108                         bp = i;
00109                         continue;
00110                 }
00111 
00112                 /* If breakpoint is enabled and has the same address
00113                  * and control bits, then reuse it.
00114                  */
00115                 if ( ( dr[i] == addr ) &&
00116                      ( ( ( dr7 ^ rwlen ) & DR7_RWLEN_MASK ( i ) ) == 0 ) ) {
00117                         bp = i;
00118                         break;
00119                 }
00120         }
00121 
00122         return bp;
00123 }
00124 
00125 /**
00126  * Set hardware breakpoint
00127  *
00128  * @v type              GDB breakpoint type
00129  * @v addr              Virtual address
00130  * @v len               Length
00131  * @v enable            Enable (not disable) breakpoint
00132  * @ret rc              Return status code
00133  */
00134 int gdbmach_set_breakpoint ( int type, unsigned long addr, size_t len,
00135                              int enable ) {
00136         unsigned int rwlen;
00137         unsigned long mask;
00138         int bp;
00139 
00140         /* Parse breakpoint type */
00141         switch ( type ) {
00142         case GDBMACH_WATCH:
00143                 rwlen = DR7_RWLEN_WRITE;
00144                 break;
00145         case GDBMACH_AWATCH:
00146                 rwlen = DR7_RWLEN_ACCESS;
00147                 break;
00148         default:
00149                 return -ENOTSUP;
00150         }
00151 
00152         /* Parse breakpoint length */
00153         switch ( len ) {
00154         case 1:
00155                 rwlen |= DR7_RWLEN_1;
00156                 break;
00157         case 2:
00158                 rwlen |= DR7_RWLEN_2;
00159                 break;
00160         case 4:
00161                 rwlen |= DR7_RWLEN_4;
00162                 break;
00163         case 8:
00164                 rwlen |= DR7_RWLEN_8;
00165                 break;
00166         default:
00167                 return -ENOTSUP;
00168         }
00169 
00170         /* Convert to linear address */
00171         if ( sizeof ( physaddr_t ) <= sizeof ( uint32_t ) )
00172                 addr = virt_to_phys ( ( void * ) addr );
00173 
00174         /* Find reusable or available hardware breakpoint */
00175         bp = gdbmach_find ( addr, rwlen );
00176         if ( bp < 0 )
00177                 return ( enable ? -ENOBUFS : 0 );
00178 
00179         /* Configure this breakpoint */
00180         DBGC ( &dr[0], "GDB bp %d at %p+%zx type %d (%sabled)\n",
00181                bp, ( ( void * ) addr ), len, type, ( enable ? "en" : "dis" ) );
00182         dr[bp] = addr;
00183         mask = DR7_RWLEN_MASK ( bp );
00184         dr7 = ( ( dr7 & ~mask ) | ( rwlen & mask ) );
00185         mask = DR7_G ( bp );
00186         dr7 &= ~mask;
00187         if ( enable )
00188                 dr7 |= mask;
00189 
00190         /* Update debug registers */
00191         gdbmach_update();
00192 
00193         return 0;
00194 }
00195 
00196 /**
00197  * Handle exception
00198  *
00199  * @v signo             GDB signal number
00200  * @v regs              Register dump
00201  */
00202 __asmcall void gdbmach_handler ( int signo, gdbreg_t *regs ) {
00203         unsigned long dr7_disabled = DR7_GE;
00204         unsigned long dr6_clear = 0;
00205 
00206         /* Temporarily disable breakpoints */
00207         __asm__ __volatile__ ( "mov %0, %%dr7\n" : : "r" ( dr7_disabled ) );
00208 
00209         /* Handle exception */
00210         DBGC ( &dr[0], "GDB signal %d\n", signo );
00211         DBGC2_HDA ( &dr[0], 0, regs, ( GDBMACH_NREGS * sizeof ( *regs ) ) );
00212         gdbstub_handler ( signo, regs );
00213         DBGC ( &dr[0], "GDB signal %d returning\n", signo );
00214         DBGC2_HDA ( &dr[0], 0, regs, ( GDBMACH_NREGS * sizeof ( *regs ) ) );
00215 
00216         /* Clear breakpoint status register */
00217         __asm__ __volatile__ ( "mov %0, %%dr6\n" : : "r" ( dr6_clear ) );
00218 
00219         /* Re-enable breakpoints */
00220         __asm__ __volatile__ ( "mov %0, %%dr7\n" : : "r" ( dr7 ) );
00221 }
00222 
00223 /**
00224  * CPU exception vectors
00225  *
00226  * Note that we cannot intercept anything from INT8 (double fault)
00227  * upwards, since these overlap by default with IRQ0-7.
00228  */
00229 static void * gdbmach_vectors[] = {
00230         gdbmach_sigfpe,         /* Divide by zero */
00231         gdbmach_sigtrap,        /* Debug trap */
00232         NULL,                   /* Non-maskable interrupt */
00233         gdbmach_sigtrap,        /* Breakpoint */
00234         gdbmach_sigstkflt,      /* Overflow */
00235         gdbmach_sigstkflt,      /* Bound range exceeded */
00236         gdbmach_sigill,         /* Invalid opcode */
00237 };
00238 
00239 /**
00240  * Initialise GDB
00241  */
00242 void gdbmach_init ( void ) {
00243         unsigned int i;
00244 
00245         /* Hook CPU exception vectors */
00246         for ( i = 0 ; i < ( sizeof ( gdbmach_vectors ) /
00247                             sizeof ( gdbmach_vectors[0] ) ) ; i++ ) {
00248                 if ( gdbmach_vectors[i] )
00249                         set_interrupt_vector ( i, gdbmach_vectors[i] );
00250         }
00251 }