all repos — mgba @ d86440e04f1dbfdcb954eeaae25ceee78ab26b65

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