all repos — mgba @ 535d179eb09955993596c30300376631a54e6a6f

mGBA Game Boy Advance Emulator

src/debugger/gdb-stub.c (view raw)

  1/* Copyright (c) 2013-2014 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include "gdb-stub.h"
  7
  8#include <errno.h>
  9#include <signal.h>
 10
 11#ifndef SIGTRAP
 12#define SIGTRAP 5 /* Win32 Signals do not include SIGTRAP */
 13#endif
 14
 15enum GDBError {
 16	GDB_NO_ERROR = 0x00,
 17	GDB_BAD_ARGUMENTS = 0x06,
 18	GDB_UNSUPPORTED_COMMAND = 0x07
 19};
 20
 21enum {
 22	MACH_O_ARM = 12,
 23	MACH_O_ARM_V4T = 5
 24};
 25
 26static void _sendMessage(struct GDBStub* stub);
 27
 28static void _gdbStubDeinit(struct ARMDebugger* debugger) {
 29	struct GDBStub* stub = (struct GDBStub*) debugger;
 30	if (!SOCKET_FAILED(stub->socket)) {
 31		GDBStubShutdown(stub);
 32	}
 33}
 34
 35static void _gdbStubEntered(struct ARMDebugger* debugger, enum DebuggerEntryReason reason) {
 36	struct GDBStub* stub = (struct GDBStub*) debugger;
 37	switch (reason) {
 38	case DEBUGGER_ENTER_MANUAL:
 39		snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGINT);
 40		break;
 41	case DEBUGGER_ENTER_BREAKPOINT:
 42	case DEBUGGER_ENTER_WATCHPOINT: // TODO: Make watchpoints raise with address
 43		snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP);
 44		break;
 45	case DEBUGGER_ENTER_ILLEGAL_OP:
 46		snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGILL);
 47		break;
 48	case DEBUGGER_ENTER_ATTACHED:
 49		return;
 50	}
 51	_sendMessage(stub);
 52}
 53
 54static void _gdbStubPoll(struct ARMDebugger* debugger) {
 55	struct GDBStub* stub = (struct GDBStub*) debugger;
 56	while (stub->d.state == DEBUGGER_PAUSED) {
 57		if (!SOCKET_FAILED(stub->connection)) {
 58			if (!SocketSetBlocking(stub->connection, 1)) {
 59				GDBStubHangup(stub);
 60				return;
 61			}
 62		}
 63		GDBStubUpdate(stub);
 64	}
 65}
 66
 67static void _ack(struct GDBStub* stub) {
 68	char ack = '+';
 69	SocketSend(stub->connection, &ack, 1);
 70}
 71
 72static void _nak(struct GDBStub* stub) {
 73	char nak = '-';
 74	if (stub->d.log) {
 75		stub->d.log(&stub->d, DEBUGGER_LOG_WARN, "Packet error");
 76	}
 77	SocketSend(stub->connection, &nak, 1);
 78}
 79
 80static uint32_t _hex2int(const char* hex, int maxDigits) {
 81	uint32_t value = 0;
 82	uint8_t letter;
 83
 84	while (maxDigits--) {
 85		letter = *hex - '0';
 86		if (letter > 9) {
 87			letter = *hex - 'a';
 88			if  (letter > 5) {
 89				break;
 90			}
 91			value *= 0x10;
 92			value += letter + 10;
 93		} else {
 94			value *= 0x10;
 95			value += letter;
 96		}
 97		++hex;
 98	}
 99	return value;
100}
101
102static void _int2hex8(uint8_t value, char* out) {
103	static const char language[] = "0123456789abcdef";
104	out[0] = language[value >> 4];
105	out[1] = language[value & 0xF];
106}
107
108static void _int2hex32(uint32_t value, char* out) {
109	static const char language[] = "0123456789abcdef";
110	out[6] = language[value >> 28];
111	out[7] = language[(value >> 24) & 0xF];
112	out[4] = language[(value >> 20) & 0xF];
113	out[5] = language[(value >> 16) & 0xF];
114	out[2] = language[(value >> 12) & 0xF];
115	out[3] = language[(value >> 8) & 0xF];
116	out[0] = language[(value >> 4) & 0xF];
117	out[1] = language[value & 0xF];
118}
119
120static uint32_t _readHex(const char* in, unsigned* out) {
121	unsigned i;
122	for (i = 0; i < 8; ++i) {
123		if (in[i] == ',') {
124			break;
125		}
126	}
127	*out += i;
128	return _hex2int(in, i);
129}
130
131static void _sendMessage(struct GDBStub* stub) {
132	if (stub->lineAck != GDB_ACK_OFF) {
133		stub->lineAck = GDB_ACK_PENDING;
134	}
135	uint8_t checksum = 0;
136	int i = 1;
137	char buffer = stub->outgoing[0];
138	char swap;
139	stub->outgoing[0] = '$';
140	if (buffer) {
141		for (; i < GDB_STUB_MAX_LINE - 5; ++i) {
142			checksum += buffer;
143			swap = stub->outgoing[i];
144			stub->outgoing[i] = buffer;
145			buffer = swap;
146			if (!buffer) {
147				++i;
148				break;
149			}
150		}
151	}
152	stub->outgoing[i] = '#';
153	_int2hex8(checksum, &stub->outgoing[i + 1]);
154	stub->outgoing[i + 3] = 0;
155	if (stub->d.log) {
156		stub->d.log(&stub->d, DEBUGGER_LOG_DEBUG, "> %s", stub->outgoing);
157	}
158	SocketSend(stub->connection, stub->outgoing, i + 3);
159}
160
161static void _error(struct GDBStub* stub, enum GDBError error) {
162	snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "E%02x", error);
163	_sendMessage(stub);
164}
165
166static void _writeHostInfo(struct GDBStub* stub) {
167	snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "cputype:%u;cpusubtype:%u:ostype:none;vendor:none;endian:little;ptrsize:4;", MACH_O_ARM, MACH_O_ARM_V4T);
168	_sendMessage(stub);
169}
170
171static void _continue(struct GDBStub* stub, const char* message) {
172	stub->d.state = DEBUGGER_RUNNING;
173	if (!SOCKET_FAILED(stub->connection)) {
174		if (!SocketSetBlocking(stub->connection, 0)) {
175			GDBStubHangup(stub);
176			return;
177		}
178	}
179	// TODO: parse message
180	UNUSED(message);
181}
182
183static void _step(struct GDBStub* stub, const char* message) {
184	ARMRun(stub->d.cpu);
185	snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP);
186	_sendMessage(stub);
187	// TODO: parse message
188	UNUSED(message);
189}
190
191static void _readMemory(struct GDBStub* stub, const char* message) {
192	const char* readAddress = message;
193	unsigned i = 0;
194	uint32_t address = _readHex(readAddress, &i);
195	readAddress += i + 1;
196	uint32_t size = _readHex(readAddress, &i);
197	if (size > 512) {
198		_error(stub, GDB_BAD_ARGUMENTS);
199		return;
200	}
201	struct ARMCore* cpu = stub->d.cpu;
202	int writeAddress = 0;
203	for (i = 0; i < size; ++i, writeAddress += 2) {
204		uint8_t byte = cpu->memory.load8(cpu, address + i, 0);
205		_int2hex8(byte, &stub->outgoing[writeAddress]);
206	}
207	stub->outgoing[writeAddress] = 0;
208	_sendMessage(stub);
209}
210
211static void _readGPRs(struct GDBStub* stub, const char* message) {
212	UNUSED(message);
213	int r;
214	int i = 0;
215	for (r = 0; r < 16; ++r) {
216		_int2hex32(stub->d.cpu->gprs[r], &stub->outgoing[i]);
217		i += 8;
218	}
219	stub->outgoing[i] = 0;
220	_sendMessage(stub);
221}
222
223static void _readRegister(struct GDBStub* stub, const char* message) {
224	const char* readAddress = message;
225	unsigned i = 0;
226	uint32_t reg = _readHex(readAddress, &i);
227	uint32_t value;
228	if (reg < 0x10) {
229		value = stub->d.cpu->gprs[reg];
230	} else if (reg == 0x19) {
231		value = stub->d.cpu->cpsr.packed;
232	} else {
233		stub->outgoing[0] = '\0';
234		_sendMessage(stub);
235		return;
236	}
237	_int2hex32(value, stub->outgoing);
238	stub->outgoing[8] = '\0';
239	_sendMessage(stub);
240}
241
242static void _processQReadCommand(struct GDBStub* stub, const char* message) {
243	stub->outgoing[0] = '\0';
244	if (!strncmp("HostInfo#", message, 9)) {
245		_writeHostInfo(stub);
246		return;
247	}
248	if (!strncmp("Attached#", message, 9)) {
249		strncpy(stub->outgoing, "1", GDB_STUB_MAX_LINE - 4);
250	} else if (!strncmp("VAttachOrWaitSupported#", message, 23)) {
251		strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
252	} else if (!strncmp("C#", message, 2)) {
253		strncpy(stub->outgoing, "QC1", GDB_STUB_MAX_LINE - 4);
254	} else if (!strncmp("fThreadInfo#", message, 12)) {
255		strncpy(stub->outgoing, "m1", GDB_STUB_MAX_LINE - 4);
256	} else if (!strncmp("sThreadInfo#", message, 12)) {
257		strncpy(stub->outgoing, "l", GDB_STUB_MAX_LINE - 4);
258	}
259	_sendMessage(stub);
260}
261
262static void _processQWriteCommand(struct GDBStub* stub, const char* message) {
263	stub->outgoing[0] = '\0';
264	if (!strncmp("StartNoAckMode#", message, 16)) {
265		stub->lineAck = GDB_ACK_OFF;
266		strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
267	}
268	_sendMessage(stub);
269}
270
271static void _processVWriteCommand(struct GDBStub* stub, const char* message) {
272	UNUSED(message);
273	stub->outgoing[0] = '\0';
274	_sendMessage(stub);
275}
276
277static void _processVReadCommand(struct GDBStub* stub, const char* message) {
278	stub->outgoing[0] = '\0';
279	if (!strncmp("Attach", message, 6)) {
280		strncpy(stub->outgoing, "1", GDB_STUB_MAX_LINE - 4);
281		ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL);
282	}
283	_sendMessage(stub);
284}
285
286static void _setBreakpoint(struct GDBStub* stub, const char* message) {
287	const char* readAddress = &message[2];
288	unsigned i = 0;
289	uint32_t address = _readHex(readAddress, &i);
290	readAddress += i + 1;
291	uint32_t kind = _readHex(readAddress, &i); // We don't use this in hardware watchpoints
292	UNUSED(kind);
293
294	switch (message[0]) {
295	case '0': // Memory breakpoints are not currently supported
296	case '1':
297		ARMDebuggerSetBreakpoint(&stub->d, address);
298		strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
299		_sendMessage(stub);
300		break;
301	case '2':
302	case '3':
303	case '4':
304		ARMDebuggerSetWatchpoint(&stub->d, address);
305		strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
306		_sendMessage(stub);
307		break;
308	default:
309		stub->outgoing[0] = '\0';
310		_sendMessage(stub);
311		break;
312	}
313}
314
315static void _clearBreakpoint(struct GDBStub* stub, const char* message) {
316	const char* readAddress = &message[2];
317	unsigned i = 0;
318	uint32_t address = _readHex(readAddress, &i);
319	switch (message[0]) {
320	case '0': // Memory breakpoints are not currently supported
321	case '1':
322		ARMDebuggerClearBreakpoint(&stub->d, address);
323		break;
324	case '2':
325	case '3':
326	case '4':
327		ARMDebuggerClearWatchpoint(&stub->d, address);
328		break;
329	default:
330		break;
331	}
332	strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
333	_sendMessage(stub);
334}
335
336size_t _parseGDBMessage(struct GDBStub* stub, const char* message) {
337	uint8_t checksum = 0;
338	int parsed = 1;
339	switch (*message) {
340	case '+':
341		stub->lineAck = GDB_ACK_RECEIVED;
342		return parsed;
343	case '-':
344		stub->lineAck = GDB_NAK_RECEIVED;
345		return parsed;
346	case '$':
347		++message;
348		break;
349	case '\x03':
350		ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL);
351		return parsed;
352	default:
353		_nak(stub);
354		return parsed;
355	}
356
357	int i;
358	char messageType = message[0];
359	for (i = 0; message[i] && message[i] != '#'; ++i, ++parsed) {
360		checksum += message[i];
361	}
362	if (!message[i]) {
363		_nak(stub);
364		return parsed;
365	}
366	++i;
367	++parsed;
368	if (!message[i]) {
369		_nak(stub);
370		return parsed;
371	} else if (!message[i + 1]) {
372		++parsed;
373		_nak(stub);
374		return parsed;
375	}
376	parsed += 2;
377	int networkChecksum = _hex2int(&message[i], 2);
378	if (networkChecksum != checksum) {
379		if (stub->d.log) {
380			stub->d.log(&stub->d, DEBUGGER_LOG_WARN, "Checksum error: expected %02x, got %02x", checksum, networkChecksum);
381		}
382		_nak(stub);
383		return parsed;
384	}
385
386	_ack(stub);
387	++message;
388	switch (messageType) {
389	case '?':
390		snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGINT);
391		_sendMessage(stub);
392		break;
393	case 'c':
394		_continue(stub, message);
395		break;
396	case 'g':
397		_readGPRs(stub, message);
398		break;
399	case 'H':
400		// This is faked because we only have one thread
401		strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
402		_sendMessage(stub);
403		break;
404	case 'm':
405		_readMemory(stub, message);
406		break;
407	case 'p':
408		_readRegister(stub, message);
409		break;
410	case 'Q':
411		_processQWriteCommand(stub, message);
412		break;
413	case 'q':
414		_processQReadCommand(stub, message);
415		break;
416	case 's':
417		_step(stub, message);
418		break;
419	case 'V':
420		_processVWriteCommand(stub, message);
421		break;
422	case 'v':
423		_processVReadCommand(stub, message);
424		break;
425	case 'Z':
426		_setBreakpoint(stub, message);
427		break;
428	case 'z':
429		_clearBreakpoint(stub, message);
430		break;
431	default:
432		_error(stub, GDB_UNSUPPORTED_COMMAND);
433		break;
434	}
435	return parsed;
436}
437
438void GDBStubCreate(struct GDBStub* stub) {
439	ARMDebuggerCreate(&stub->d);
440	stub->socket = INVALID_SOCKET;
441	stub->connection = INVALID_SOCKET;
442	stub->d.init = 0;
443	stub->d.deinit = _gdbStubDeinit;
444	stub->d.paused = _gdbStubPoll;
445	stub->d.entered = _gdbStubEntered;
446	stub->d.log = 0;
447}
448
449int GDBStubListen(struct GDBStub* stub, int port, const struct Address* bindAddress) {
450	if (!SOCKET_FAILED(stub->socket)) {
451		GDBStubShutdown(stub);
452	}
453	stub->socket = SocketOpenTCP(port, bindAddress);
454	if (SOCKET_FAILED(stub->socket)) {
455		if (stub->d.log) {
456			stub->d.log(&stub->d, DEBUGGER_LOG_ERROR, "Couldn't open socket");
457		}
458		return 0;
459	}
460	int err = SocketListen(stub->socket, 1);
461	if (err) {
462		goto cleanup;
463	}
464	if (!SocketSetBlocking(stub->socket, 0)) {
465		goto cleanup;
466	}
467
468	return 1;
469
470cleanup:
471	if (stub->d.log) {
472		stub->d.log(&stub->d, DEBUGGER_LOG_ERROR, "Couldn't listen on port");
473	}
474	SocketClose(stub->socket);
475	stub->socket = -1;
476	return 0;
477}
478
479void GDBStubHangup(struct GDBStub* stub) {
480	if (!SOCKET_FAILED(stub->connection)) {
481		SocketClose(stub->connection);
482		stub->connection = -1;
483	}
484	if (stub->d.state == DEBUGGER_PAUSED) {
485		stub->d.state = DEBUGGER_RUNNING;
486	}
487}
488
489void GDBStubShutdown(struct GDBStub* stub) {
490	GDBStubHangup(stub);
491	if (!SOCKET_FAILED(stub->socket)) {
492		SocketClose(stub->socket);
493		stub->socket = -1;
494	}
495}
496
497void GDBStubUpdate(struct GDBStub* stub) {
498	if (stub->socket == INVALID_SOCKET) {
499		return;
500	}
501	if (stub->connection == INVALID_SOCKET) {
502		stub->connection = SocketAccept(stub->socket, 0);
503		if (!SOCKET_FAILED(stub->connection)) {
504			if (!SocketSetBlocking(stub->connection, 0)) {
505				goto connectionLost;
506			}
507			ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_ATTACHED);
508		} else if (errno == EWOULDBLOCK || errno == EAGAIN) {
509			return;
510		} else {
511			goto connectionLost;
512		}
513	}
514	while (true) {
515		ssize_t messageLen = SocketRecv(stub->connection, stub->line, GDB_STUB_MAX_LINE - 1);
516		if (messageLen == 0) {
517			goto connectionLost;
518		}
519		if (messageLen == -1) {
520			if (errno == EWOULDBLOCK || errno == EAGAIN) {
521				return;
522			}
523			goto connectionLost;
524		}
525		stub->line[messageLen] = '\0';
526		if (stub->d.log) {
527			stub->d.log(&stub->d, DEBUGGER_LOG_DEBUG, "< %s", stub->line);
528		}
529		ssize_t position = 0;
530		while (position < messageLen) {
531			position += _parseGDBMessage(stub, &stub->line[position]);
532		}
533	}
534
535connectionLost:
536	if (stub->d.log) {
537		stub->d.log(&stub->d, DEBUGGER_LOG_INFO, "Connection lost");
538	}
539	GDBStubHangup(stub);
540}