[GDB] Add watch and rwatch hardware watchpoints
[people/andreif/gpxe.git] / src / arch / i386 / core / gdbmach.c
1 #include <stddef.h>
2 #include <stdio.h>
3 #include <assert.h>
4 #include <virtaddr.h>
5 #include <gpxe/gdbstub.h>
6 #include <gdbmach.h>
7
8 enum {
9         DR7_CLEAR = 0x00000400,    /* disable hardware breakpoints */
10         DR6_CLEAR = 0xffff0ff0,    /* clear breakpoint status */
11 };
12
13 /** Hardware breakpoint, fields stored in x86 bit pattern form */
14 struct hwbp {
15         int type;           /* type (1=write watchpoint, 3=access watchpoint) */
16         unsigned long addr; /* linear address */
17         size_t len;         /* length (0=1-byte, 1=2-byte, 3=4-byte) */
18         int enabled;
19 };
20
21 static struct hwbp hwbps [ 4 ];
22 static gdbreg_t dr7 = DR7_CLEAR;
23 static gdbreg_t dr6;
24
25 static struct hwbp *gdbmach_find_hwbp ( int type, unsigned long addr, size_t len ) {
26         struct hwbp *available = NULL;
27         unsigned int i;
28         for ( i = 0; i < sizeof hwbps / sizeof hwbps [ 0 ]; i++ ) {
29                 if ( hwbps [ i ].type == type && hwbps [ i ].addr == addr && hwbps [ i ].len == len ) {
30                         return &hwbps [ i ];
31                 }
32                 if ( !hwbps [ i ].enabled ) {
33                         available = &hwbps [ i ];
34                 }
35         }
36         return available;
37 }
38
39 static void gdbmach_commit_hwbp ( struct hwbp *bp ) {
40         int regnum = bp - hwbps;
41
42         /* Set breakpoint address */
43         assert ( regnum >= 0 && regnum < sizeof hwbps / sizeof hwbps [ 0 ] );
44         switch ( regnum ) {
45                 case 0:
46                         __asm__ __volatile__ ( "movl %0, %%dr0\n" : : "r" ( bp->addr ) );
47                         break;
48                 case 1:
49                         __asm__ __volatile__ ( "movl %0, %%dr1\n" : : "r" ( bp->addr ) );
50                         break;
51                 case 2:
52                         __asm__ __volatile__ ( "movl %0, %%dr2\n" : : "r" ( bp->addr ) );
53                         break;
54                 case 3:
55                         __asm__ __volatile__ ( "movl %0, %%dr3\n" : : "r" ( bp->addr ) );
56                         break;
57         }
58
59         /* Set type */
60         dr7 &= ~( 0x3 << ( 16 + 4 * regnum ) );
61         dr7 |= bp->type << ( 16 + 4 * regnum );
62
63         /* Set length */
64         dr7 &= ~( 0x3 << ( 18 + 4 * regnum ) );
65         dr7 |= bp->len << ( 18 + 4 * regnum );
66
67         /* Set/clear local enable bit */
68         dr7 &= ~( 0x3 << 2 * regnum );
69         dr7 |= bp->enabled << 2 * regnum;
70 }
71
72 int gdbmach_set_breakpoint ( int type, unsigned long addr, size_t len, int enable ) {
73         struct hwbp *bp;
74         
75         /* Check and convert breakpoint type to x86 type */
76         switch ( type ) {
77                 case GDBMACH_WATCH:
78                         type = 0x1;
79                         break;
80                 case GDBMACH_AWATCH:
81                         type = 0x3;
82                         break;
83                 default:
84                         return 0; /* unsupported breakpoint type */
85         }
86
87         /* Only lengths 1, 2, and 4 are supported */
88         if ( len != 2 && len != 4 ) {
89                 len = 1;
90         }
91         len--; /* convert to x86 breakpoint length bit pattern */
92
93         /* Calculate linear address by adding segment base */
94         addr += virt_offset;
95
96         /* Set up the breakpoint */
97         bp = gdbmach_find_hwbp ( type, addr, len );
98         if ( !bp ) {
99                 return 0; /* ran out of hardware breakpoints */
100         }
101         bp->type = type;
102         bp->addr = addr;
103         bp->len = len;
104         bp->enabled = enable;
105         gdbmach_commit_hwbp ( bp );
106         return 1;
107 }
108
109 static void gdbmach_disable_hwbps ( void ) {
110         /* Store and clear breakpoint status register */
111         __asm__ __volatile__ ( "movl %%dr6, %0\n" "movl %1, %%dr6\n" : "=r" ( dr6 ) : "r" ( DR6_CLEAR ) );
112
113         /* Store and clear hardware breakpoints */
114         __asm__ __volatile__ ( "movl %0, %%dr7\n" : : "r" ( DR7_CLEAR ) );
115 }
116
117 static void gdbmach_enable_hwbps ( void ) {
118         /* Restore hardware breakpoints */
119         __asm__ __volatile__ ( "movl %0, %%dr7\n" : : "r" ( dr7 ) );
120 }
121
122 __cdecl void gdbmach_handler ( int signo, gdbreg_t *regs ) {
123         gdbmach_disable_hwbps();
124         gdbstub_handler ( signo, regs );
125         gdbmach_enable_hwbps();
126 }