diff options
| author | Michael Brown | 2016-03-20 13:00:15 +0100 |
|---|---|---|
| committer | Michael Brown | 2016-03-22 09:44:32 +0100 |
| commit | 311a5732c8baa7ceb4f23db51dcbb5015e2ef965 (patch) | |
| tree | dcdb0f60b43c40b6512bb8a3fcd93e0e9478c41d /src/arch/x86/core | |
| parent | [build] Do not use "objcopy -O binary" for objects with relocation records (diff) | |
| download | ipxe-311a5732c8baa7ceb4f23db51dcbb5015e2ef965.tar.gz ipxe-311a5732c8baa7ceb4f23db51dcbb5015e2ef965.tar.xz ipxe-311a5732c8baa7ceb4f23db51dcbb5015e2ef965.zip | |
[gdb] Add support for x86_64
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/arch/x86/core')
| -rw-r--r-- | src/arch/x86/core/gdbmach.c | 251 |
1 files changed, 251 insertions, 0 deletions
diff --git a/src/arch/x86/core/gdbmach.c b/src/arch/x86/core/gdbmach.c new file mode 100644 index 000000000..af6abfedd --- /dev/null +++ b/src/arch/x86/core/gdbmach.c @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2008 Stefan Hajnoczi <stefanha@gmail.com>. + * Copyright (C) 2016 Michael Brown <mbrown@fensystems.co.uk>. + * + * 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 <stddef.h> +#include <stdio.h> +#include <errno.h> +#include <assert.h> +#include <ipxe/uaccess.h> +#include <ipxe/gdbstub.h> +#include <librm.h> +#include <gdbmach.h> + +/** @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] ); + } +} |
