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 */ | ||||
| 	pushl	%esp | ||||
| 	pushl	(IH_OFFSET_SIGNO + 4)(%esp) | ||||
| 	call	gdbstub_handler | ||||
| 	call	gdbmach_handler | ||||
| 	addl	$8, %esp | ||||
| 
 | ||||
| 	/* 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; | ||||
| 
 | ||||
| /* 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 ) | ||||
| }; | ||||
| 
 | ||||
| /* 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 ) { | ||||
| 	regs [ GDBMACH_EIP ] = pc; | ||||
| } | ||||
|  | @ -48,4 +59,6 @@ static inline void gdbmach_breakpoint ( void ) { | |||
| 	__asm__ __volatile__ ( "int $3\n" ); | ||||
| } | ||||
| 
 | ||||
| extern int gdbmach_set_breakpoint ( int type, unsigned long addr, size_t len, int enable ); | ||||
| 
 | ||||
| #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 */ | ||||
| } | ||||
| 
 | ||||
| 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 ) { | ||||
| 	switch ( stub->payload [ 0 ] ) { | ||||
| 		case '?': | ||||
|  | @ -275,6 +291,10 @@ static void gdbstub_rx_packet ( struct gdbstub *stub ) { | |||
| 				gdbstub_send_ok ( stub ); | ||||
| 			} | ||||
| 			break; | ||||
| 		case 'Z': /* Insert breakpoint */ | ||||
| 		case 'z': /* Remove breakpoint */ | ||||
| 			gdbstub_breakpoint ( stub ); | ||||
| 			break; | ||||
| 		default: | ||||
| 			stub->len = 0; | ||||
| 			gdbstub_tx_packet ( stub ); | ||||
|  | @ -341,7 +361,7 @@ static struct gdbstub stub = { | |||
| 	.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 ]; | ||||
| 	size_t len, i; | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ | |||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <gpxe/tables.h> | ||||
| #include <gdbmach.h> | ||||
| 
 | ||||
| /**
 | ||||
|  * 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 ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Interrupt handler | ||||
|  * | ||||
|  * @signo POSIX signal number | ||||
|  * @regs CPU register snapshot | ||||
|  **/ | ||||
| extern void gdbstub_handler ( int signo, gdbreg_t *regs ); | ||||
| 
 | ||||
| #endif /* _GPXE_GDBSTUB_H */ | ||||
|  |  | |||
|  | @ -1,4 +1,9 @@ | |||
| 	.arch i386
 | ||||
| 
 | ||||
| 	.section ".data" | ||||
| watch_me: | ||||
| 	.long 0xfeedbeef
 | ||||
| 
 | ||||
| 	.section ".text" | ||||
| 	.code32 | ||||
| gdbstub_test: | ||||
|  | @ -29,5 +34,21 @@ gdbstub_test: | |||
| 	int	$3 | ||||
| 	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: | ||||
| 	jmp	1b | ||||
|  |  | |||
|  | @ -77,6 +77,34 @@ define gpxe_test_step | |||
| 	gpxe_assert ({char}($eip-1)) (char)0x90 "gpxe_test_step" # nop = 0x90 | ||||
| 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_start_tests | ||||
| gpxe_test_regs_read | ||||
|  | @ -84,3 +112,5 @@ gpxe_test_regs_write | |||
| gpxe_test_mem_read | ||||
| gpxe_test_mem_write | ||||
| gpxe_test_step | ||||
| gpxe_test_awatch | ||||
| gpxe_test_watch | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue