mirror of https://github.com/ipxe/ipxe.git
[GDB] Add watch and rwatch hardware watchpoints
parent
6e670b5f38
commit
19386ec2c8
|
@ -184,7 +184,7 @@ do_interrupt:
|
||||||
/* Call GDB stub exception handler */
|
/* Call GDB stub exception handler */
|
||||||
pushl %esp
|
pushl %esp
|
||||||
pushl (IH_OFFSET_SIGNO + 4)(%esp)
|
pushl (IH_OFFSET_SIGNO + 4)(%esp)
|
||||||
call gdbstub_handler
|
call gdbmach_handler
|
||||||
addl $8, %esp
|
addl $8, %esp
|
||||||
|
|
||||||
/* Restore CPU state from GDB register snapshot */
|
/* Restore CPU state from GDB register snapshot */
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <virtaddr.h>
|
||||||
|
#include <gpxe/gdbstub.h>
|
||||||
|
#include <gdbmach.h>
|
||||||
|
|
||||||
|
enum {
|
||||||
|
DR7_CLEAR = 0x00000400, /* disable hardware breakpoints */
|
||||||
|
DR6_CLEAR = 0xffff0ff0, /* clear breakpoint status */
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Hardware breakpoint, fields stored in x86 bit pattern form */
|
||||||
|
struct hwbp {
|
||||||
|
int type; /* type (1=write watchpoint, 3=access watchpoint) */
|
||||||
|
unsigned long addr; /* linear address */
|
||||||
|
size_t len; /* length (0=1-byte, 1=2-byte, 3=4-byte) */
|
||||||
|
int enabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct hwbp hwbps [ 4 ];
|
||||||
|
static gdbreg_t dr7 = DR7_CLEAR;
|
||||||
|
static gdbreg_t dr6;
|
||||||
|
|
||||||
|
static struct hwbp *gdbmach_find_hwbp ( int type, unsigned long addr, size_t len ) {
|
||||||
|
struct hwbp *available = NULL;
|
||||||
|
unsigned int i;
|
||||||
|
for ( i = 0; i < sizeof hwbps / sizeof hwbps [ 0 ]; i++ ) {
|
||||||
|
if ( hwbps [ i ].type == type && hwbps [ i ].addr == addr && hwbps [ i ].len == len ) {
|
||||||
|
return &hwbps [ i ];
|
||||||
|
}
|
||||||
|
if ( !hwbps [ i ].enabled ) {
|
||||||
|
available = &hwbps [ i ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return available;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gdbmach_commit_hwbp ( struct hwbp *bp ) {
|
||||||
|
int regnum = bp - hwbps;
|
||||||
|
|
||||||
|
/* Set breakpoint address */
|
||||||
|
assert ( regnum >= 0 && regnum < sizeof hwbps / sizeof hwbps [ 0 ] );
|
||||||
|
switch ( regnum ) {
|
||||||
|
case 0:
|
||||||
|
__asm__ __volatile__ ( "movl %0, %%dr0\n" : : "r" ( bp->addr ) );
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
__asm__ __volatile__ ( "movl %0, %%dr1\n" : : "r" ( bp->addr ) );
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
__asm__ __volatile__ ( "movl %0, %%dr2\n" : : "r" ( bp->addr ) );
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
__asm__ __volatile__ ( "movl %0, %%dr3\n" : : "r" ( bp->addr ) );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set type */
|
||||||
|
dr7 &= ~( 0x3 << ( 16 + 4 * regnum ) );
|
||||||
|
dr7 |= bp->type << ( 16 + 4 * regnum );
|
||||||
|
|
||||||
|
/* Set length */
|
||||||
|
dr7 &= ~( 0x3 << ( 18 + 4 * regnum ) );
|
||||||
|
dr7 |= bp->len << ( 18 + 4 * regnum );
|
||||||
|
|
||||||
|
/* Set/clear local enable bit */
|
||||||
|
dr7 &= ~( 0x3 << 2 * regnum );
|
||||||
|
dr7 |= bp->enabled << 2 * regnum;
|
||||||
|
}
|
||||||
|
|
||||||
|
int gdbmach_set_breakpoint ( int type, unsigned long addr, size_t len, int enable ) {
|
||||||
|
struct hwbp *bp;
|
||||||
|
|
||||||
|
/* Check and convert breakpoint type to x86 type */
|
||||||
|
switch ( type ) {
|
||||||
|
case GDBMACH_WATCH:
|
||||||
|
type = 0x1;
|
||||||
|
break;
|
||||||
|
case GDBMACH_AWATCH:
|
||||||
|
type = 0x3;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return 0; /* unsupported breakpoint type */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Only lengths 1, 2, and 4 are supported */
|
||||||
|
if ( len != 2 && len != 4 ) {
|
||||||
|
len = 1;
|
||||||
|
}
|
||||||
|
len--; /* convert to x86 breakpoint length bit pattern */
|
||||||
|
|
||||||
|
/* Calculate linear address by adding segment base */
|
||||||
|
addr += virt_offset;
|
||||||
|
|
||||||
|
/* Set up the breakpoint */
|
||||||
|
bp = gdbmach_find_hwbp ( type, addr, len );
|
||||||
|
if ( !bp ) {
|
||||||
|
return 0; /* ran out of hardware breakpoints */
|
||||||
|
}
|
||||||
|
bp->type = type;
|
||||||
|
bp->addr = addr;
|
||||||
|
bp->len = len;
|
||||||
|
bp->enabled = enable;
|
||||||
|
gdbmach_commit_hwbp ( bp );
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gdbmach_disable_hwbps ( void ) {
|
||||||
|
/* Store and clear breakpoint status register */
|
||||||
|
__asm__ __volatile__ ( "movl %%dr6, %0\n" "movl %1, %%dr6\n" : "=r" ( dr6 ) : "r" ( DR6_CLEAR ) );
|
||||||
|
|
||||||
|
/* Store and clear hardware breakpoints */
|
||||||
|
__asm__ __volatile__ ( "movl %0, %%dr7\n" : : "r" ( DR7_CLEAR ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gdbmach_enable_hwbps ( void ) {
|
||||||
|
/* Restore hardware breakpoints */
|
||||||
|
__asm__ __volatile__ ( "movl %0, %%dr7\n" : : "r" ( dr7 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
__cdecl void gdbmach_handler ( int signo, gdbreg_t *regs ) {
|
||||||
|
gdbmach_disable_hwbps();
|
||||||
|
gdbstub_handler ( signo, regs );
|
||||||
|
gdbmach_enable_hwbps();
|
||||||
|
}
|
|
@ -10,6 +10,8 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
typedef uint32_t gdbreg_t;
|
typedef uint32_t gdbreg_t;
|
||||||
|
|
||||||
/* The register snapshot, this must be in sync with interrupt handler and the
|
/* The register snapshot, this must be in sync with interrupt handler and the
|
||||||
|
@ -35,6 +37,15 @@ enum {
|
||||||
GDBMACH_SIZEOF_REGS = GDBMACH_NREGS * sizeof ( gdbreg_t )
|
GDBMACH_SIZEOF_REGS = GDBMACH_NREGS * sizeof ( gdbreg_t )
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Breakpoint types */
|
||||||
|
enum {
|
||||||
|
GDBMACH_BPMEM,
|
||||||
|
GDBMACH_BPHW,
|
||||||
|
GDBMACH_WATCH,
|
||||||
|
GDBMACH_RWATCH,
|
||||||
|
GDBMACH_AWATCH,
|
||||||
|
};
|
||||||
|
|
||||||
static inline void gdbmach_set_pc ( gdbreg_t *regs, gdbreg_t pc ) {
|
static inline void gdbmach_set_pc ( gdbreg_t *regs, gdbreg_t pc ) {
|
||||||
regs [ GDBMACH_EIP ] = pc;
|
regs [ GDBMACH_EIP ] = pc;
|
||||||
}
|
}
|
||||||
|
@ -48,4 +59,6 @@ static inline void gdbmach_breakpoint ( void ) {
|
||||||
__asm__ __volatile__ ( "int $3\n" );
|
__asm__ __volatile__ ( "int $3\n" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern int gdbmach_set_breakpoint ( int type, unsigned long addr, size_t len, int enable );
|
||||||
|
|
||||||
#endif /* GDBMACH_H */
|
#endif /* GDBMACH_H */
|
||||||
|
|
|
@ -249,6 +249,22 @@ static void gdbstub_continue ( struct gdbstub *stub, int single_step ) {
|
||||||
/* Reply will be sent when we hit the next breakpoint or interrupt */
|
/* Reply will be sent when we hit the next breakpoint or interrupt */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void gdbstub_breakpoint ( struct gdbstub *stub ) {
|
||||||
|
unsigned long args [ 3 ];
|
||||||
|
int enable = stub->payload [ 0 ] == 'Z' ? 1 : 0;
|
||||||
|
if ( !gdbstub_get_packet_args ( stub, args, sizeof args / sizeof args [ 0 ], NULL ) ) {
|
||||||
|
gdbstub_send_errno ( stub, POSIX_EINVAL );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( gdbmach_set_breakpoint ( args [ 0 ], args [ 1 ], args [ 2 ], enable ) ) {
|
||||||
|
gdbstub_send_ok ( stub );
|
||||||
|
} else {
|
||||||
|
/* Not supported */
|
||||||
|
stub->len = 0;
|
||||||
|
gdbstub_tx_packet ( stub );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void gdbstub_rx_packet ( struct gdbstub *stub ) {
|
static void gdbstub_rx_packet ( struct gdbstub *stub ) {
|
||||||
switch ( stub->payload [ 0 ] ) {
|
switch ( stub->payload [ 0 ] ) {
|
||||||
case '?':
|
case '?':
|
||||||
|
@ -275,6 +291,10 @@ static void gdbstub_rx_packet ( struct gdbstub *stub ) {
|
||||||
gdbstub_send_ok ( stub );
|
gdbstub_send_ok ( stub );
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'Z': /* Insert breakpoint */
|
||||||
|
case 'z': /* Remove breakpoint */
|
||||||
|
gdbstub_breakpoint ( stub );
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
stub->len = 0;
|
stub->len = 0;
|
||||||
gdbstub_tx_packet ( stub );
|
gdbstub_tx_packet ( stub );
|
||||||
|
@ -341,7 +361,7 @@ static struct gdbstub stub = {
|
||||||
.parse = gdbstub_state_new
|
.parse = gdbstub_state_new
|
||||||
};
|
};
|
||||||
|
|
||||||
__cdecl void gdbstub_handler ( int signo, gdbreg_t *regs ) {
|
void gdbstub_handler ( int signo, gdbreg_t *regs ) {
|
||||||
char packet [ SIZEOF_PAYLOAD + 4 ];
|
char packet [ SIZEOF_PAYLOAD + 4 ];
|
||||||
size_t len, i;
|
size_t len, i;
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <gpxe/tables.h>
|
#include <gpxe/tables.h>
|
||||||
|
#include <gdbmach.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A transport mechanism for the GDB protocol
|
* A transport mechanism for the GDB protocol
|
||||||
|
@ -61,4 +62,12 @@ extern struct gdb_transport *find_gdb_transport ( const char *name );
|
||||||
*/
|
*/
|
||||||
extern void gdbstub_start ( struct gdb_transport *trans );
|
extern void gdbstub_start ( struct gdb_transport *trans );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interrupt handler
|
||||||
|
*
|
||||||
|
* @signo POSIX signal number
|
||||||
|
* @regs CPU register snapshot
|
||||||
|
**/
|
||||||
|
extern void gdbstub_handler ( int signo, gdbreg_t *regs );
|
||||||
|
|
||||||
#endif /* _GPXE_GDBSTUB_H */
|
#endif /* _GPXE_GDBSTUB_H */
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
.arch i386
|
.arch i386
|
||||||
|
|
||||||
|
.section ".data"
|
||||||
|
watch_me:
|
||||||
|
.long 0xfeedbeef
|
||||||
|
|
||||||
.section ".text"
|
.section ".text"
|
||||||
.code32
|
.code32
|
||||||
gdbstub_test:
|
gdbstub_test:
|
||||||
|
@ -29,5 +34,21 @@ gdbstub_test:
|
||||||
int $3
|
int $3
|
||||||
nop
|
nop
|
||||||
|
|
||||||
|
/* 6. Access watch test */
|
||||||
|
movl $0x600d0000, %ecx
|
||||||
|
movl watch_me, %eax
|
||||||
|
movl $0xbad00000, %ecx
|
||||||
|
int $3
|
||||||
|
movl $0x600d0001, %ecx
|
||||||
|
movl %eax, watch_me
|
||||||
|
movl $0xbad00001, %ecx
|
||||||
|
int $3
|
||||||
|
|
||||||
|
/* 7. Write watch test */
|
||||||
|
movl $0x600d0002, %ecx
|
||||||
|
movl %eax, watch_me
|
||||||
|
movl $0xbad00002, %ecx
|
||||||
|
int $3
|
||||||
|
|
||||||
1:
|
1:
|
||||||
jmp 1b
|
jmp 1b
|
||||||
|
|
|
@ -77,6 +77,34 @@ define gpxe_test_step
|
||||||
gpxe_assert ({char}($eip-1)) (char)0x90 "gpxe_test_step" # nop = 0x90
|
gpxe_assert ({char}($eip-1)) (char)0x90 "gpxe_test_step" # nop = 0x90
|
||||||
end
|
end
|
||||||
|
|
||||||
|
define gpxe_test_awatch
|
||||||
|
awatch watch_me
|
||||||
|
|
||||||
|
c
|
||||||
|
gpxe_assert $ecx 0x600d0000 "gpxe_test_awatch"
|
||||||
|
if $ecx == 0x600d0000
|
||||||
|
c
|
||||||
|
end
|
||||||
|
|
||||||
|
c
|
||||||
|
gpxe_assert $ecx 0x600d0001 "gpxe_test_awatch"
|
||||||
|
if $ecx == 0x600d0001
|
||||||
|
c
|
||||||
|
end
|
||||||
|
|
||||||
|
delete
|
||||||
|
end
|
||||||
|
|
||||||
|
define gpxe_test_watch
|
||||||
|
watch watch_me
|
||||||
|
c
|
||||||
|
gpxe_assert $ecx 0x600d0002 "gpxe_test_watch"
|
||||||
|
if $ecx == 0x600d0002
|
||||||
|
c
|
||||||
|
end
|
||||||
|
delete
|
||||||
|
end
|
||||||
|
|
||||||
gpxe_load_symbols
|
gpxe_load_symbols
|
||||||
gpxe_start_tests
|
gpxe_start_tests
|
||||||
gpxe_test_regs_read
|
gpxe_test_regs_read
|
||||||
|
@ -84,3 +112,5 @@ gpxe_test_regs_write
|
||||||
gpxe_test_mem_read
|
gpxe_test_mem_read
|
||||||
gpxe_test_mem_write
|
gpxe_test_mem_write
|
||||||
gpxe_test_step
|
gpxe_test_step
|
||||||
|
gpxe_test_awatch
|
||||||
|
gpxe_test_watch
|
||||||
|
|
Loading…
Reference in New Issue