summaryrefslogtreecommitdiffstats
path: root/src/arch/x86/core/gdbmach.c
blob: af6abfedd6c48eb8d0313fa39a3eda4bc3b14649 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
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] );
	}
}