all repos — mgba @ c937529d4ab5bb328923cc7654de5518ce8c111e

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, uint32_t bindAddress) {
450	if (!SOCKET_FAILED(stub->socket)) {
451		GDBStubShutdown(stub);
452	}
453	// TODO: support IPv6
454	stub->socket = SocketOpenTCP(port, bindAddress);
455	if (SOCKET_FAILED(stub->socket)) {
456		if (stub->d.log) {
457			stub->d.log(&stub->d, DEBUGGER_LOG_ERROR, "Couldn't open socket");
458		}
459		return 0;
460	}
461	int err = SocketListen(stub->socket, 1);
462	if (err) {
463		goto cleanup;
464	}
465	if (!SocketSetBlocking(stub->socket, 0)) {
466		goto cleanup;
467	}
468
469	return 1;
470
471cleanup:
472	if (stub->d.log) {
473		stub->d.log(&stub->d, DEBUGGER_LOG_ERROR, "Couldn't listen on port");
474	}
475	SocketClose(stub->socket);
476	stub->socket = -1;
477	return 0;
478}
479
480void GDBStubHangup(struct GDBStub* stub) {
481	if (!SOCKET_FAILED(stub->connection)) {
482		SocketClose(stub->connection);
483		stub->connection = -1;
484	}
485	if (stub->d.state == DEBUGGER_PAUSED) {
486		stub->d.state = DEBUGGER_RUNNING;
487	}
488}
489
490void GDBStubShutdown(struct GDBStub* stub) {
491	GDBStubHangup(stub);
492	if (!SOCKET_FAILED(stub->socket)) {
493		SocketClose(stub->socket);
494		stub->socket = -1;
495	}
496}
497
498void GDBStubUpdate(struct GDBStub* stub) {
499	if (stub->socket == INVALID_SOCKET) {
500		return;
501	}
502	if (stub->connection == INVALID_SOCKET) {
503		stub->connection = SocketAccept(stub->socket, 0, 0);
504		if (!SOCKET_FAILED(stub->connection)) {
505			if (!SocketSetBlocking(stub->connection, 0)) {
506				goto connectionLost;
507			}
508			ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_ATTACHED);
509		} else if (errno == EWOULDBLOCK || errno == EAGAIN) {
510			return;
511		} else {
512			goto connectionLost;
513		}
514	}
515	while (true) {
516		ssize_t messageLen = SocketRecv(stub->connection, stub->line, GDB_STUB_MAX_LINE - 1);
517		if (messageLen == 0) {
518			goto connectionLost;
519		}
520		if (messageLen == -1) {
521			if (errno == EWOULDBLOCK || errno == EAGAIN) {
522				return;
523			}
524			goto connectionLost;
525		}
526		stub->line[messageLen] = '\0';
527		if (stub->d.log) {
528			stub->d.log(&stub->d, DEBUGGER_LOG_DEBUG, "< %s", stub->line);
529		}
530		ssize_t position = 0;
531		while (position < messageLen) {
532			position += _parseGDBMessage(stub, &stub->line[position]);
533		}
534	}
535
536connectionLost:
537	if (stub->d.log) {
538		stub->d.log(&stub->d, DEBUGGER_LOG_INFO, "Connection lost");
539	}
540	GDBStubHangup(stub);
541}