all repos — mgba @ b6f1ddc4fee136f02ad261b324d34c7f194d7b3a

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