src/debugger/gdb-stub.c (view raw)
1#include "gdb-stub.h"
2
3#include <errno.h>
4#include <fcntl.h>
5#include <netinet/in.h>
6#include <signal.h>
7#include <stdio.h>
8#include <string.h>
9#include <sys/socket.h>
10#include <unistd.h>
11
12enum GDBError {
13 GDB_NO_ERROR = 0x00,
14 GDB_BAD_ARGUMENTS = 0x06,
15 GDB_UNSUPPORTED_COMMAND = 0x07
16};
17
18enum {
19 MACH_O_ARM = 12,
20 MACH_O_ARM_V4T = 5
21};
22
23static void _sendMessage(struct GDBStub* stub);
24
25static void _gdbStubDeinit(struct ARMDebugger* debugger) {
26 struct GDBStub* stub = (struct GDBStub*) debugger;
27 if (stub->socket >= 0) {
28 GDBStubShutdown(stub);
29 }
30}
31
32static void _gdbStubEntered(struct ARMDebugger* debugger, enum DebuggerEntryReason reason) {
33 struct GDBStub* stub = (struct GDBStub*) debugger;
34 switch (reason) {
35 case DEBUGGER_ENTER_MANUAL:
36 snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGINT);
37 break;
38 case DEBUGGER_ENTER_BREAKPOINT:
39 case DEBUGGER_ENTER_WATCHPOINT: // TODO: Make watchpoints raise with address
40 snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP);
41 break;
42 case DEBUGGER_ENTER_ILLEGAL_OP:
43 snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGILL);
44 break;
45 case DEBUGGER_ENTER_ATTACHED:
46 return;
47 }
48 _sendMessage(stub);
49}
50
51static void _gdbStubPoll(struct ARMDebugger* debugger) {
52 struct GDBStub* stub = (struct GDBStub*) debugger;
53 int flags;
54 while (stub->d.state == DEBUGGER_PAUSED) {
55 if (stub->connection >= 0) {
56 flags = fcntl(stub->connection, F_GETFL);
57 if (flags == -1) {
58 GDBStubHangup(stub);
59 return;
60 }
61 fcntl(stub->connection, F_SETFL, flags & ~O_NONBLOCK);
62 }
63 GDBStubUpdate(stub);
64 }
65}
66
67static void _ack(struct GDBStub* stub) {
68 char ack = '+';
69 send(stub->connection, &ack, 1, 0);
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 send(stub->connection, &nak, 1, 0);
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 send(stub->connection, stub->outgoing, i + 3, 0);
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 (stub->connection >= 0) {
174 int flags = fcntl(stub->connection, F_GETFL);
175 if (flags == -1) {
176 GDBStubHangup(stub);
177 return;
178 }
179 fcntl(stub->connection, F_SETFL, flags | O_NONBLOCK);
180 }
181 // TODO: parse message
182 (void) (message);
183}
184
185static void _step(struct GDBStub* stub, const char* message) {
186 ARMRun(stub->d.cpu);
187 snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGINT);
188 _sendMessage(stub);
189 // TODO: parse message
190 (void) (message);
191}
192
193static void _readMemory(struct GDBStub* stub, const char* message) {
194 const char* readAddress = message;
195 unsigned i = 0;
196 uint32_t address = _readHex(readAddress, &i);
197 readAddress += i + 1;
198 uint32_t size = _readHex(readAddress, &i);
199 if (size > 512) {
200 _error(stub, GDB_BAD_ARGUMENTS);
201 return;
202 }
203 struct ARMMemory* memory = stub->d.memoryShim.original;
204 int writeAddress = 0;
205 for (i = 0; i < size; ++i, writeAddress += 2) {
206 uint8_t byte = memory->load8(memory, address + i, 0);
207 _int2hex8(byte, &stub->outgoing[writeAddress]);
208 }
209 stub->outgoing[writeAddress] = 0;
210 _sendMessage(stub);
211}
212
213static void _readGPRs(struct GDBStub* stub, const char* message) {
214 (void) (message);
215 int r;
216 int i = 0;
217 for (r = 0; r < 16; ++r) {
218 _int2hex32(stub->d.cpu->gprs[r], &stub->outgoing[i]);
219 i += 8;
220 }
221 stub->outgoing[i] = 0;
222 _sendMessage(stub);
223}
224
225static void _readRegister(struct GDBStub* stub, const char* message) {
226 const char* readAddress = message;
227 unsigned i = 0;
228 uint32_t reg = _readHex(readAddress, &i);
229 uint32_t value;
230 if (reg < 0x10) {
231 value = stub->d.cpu->gprs[reg];
232 } else if (reg == 0x19) {
233 value = stub->d.cpu->cpsr.packed;
234 } else {
235 stub->outgoing[0] = '\0';
236 _sendMessage(stub);
237 return;
238 }
239 _int2hex32(value, stub->outgoing);
240 stub->outgoing[8] = '\0';
241 _sendMessage(stub);
242}
243
244static void _processQReadCommand(struct GDBStub* stub, const char* message) {
245 stub->outgoing[0] = '\0';
246 if (!strncmp("HostInfo#", message, 9)) {
247 _writeHostInfo(stub);
248 return;
249 }
250 if (!strncmp("Attached#", message, 9)) {
251 strncpy(stub->outgoing, "1", GDB_STUB_MAX_LINE - 4);
252 } else if (!strncmp("VAttachOrWaitSupported#", message, 23)) {
253 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
254 } else if (!strncmp("C#", message, 2)) {
255 strncpy(stub->outgoing, "QC1", GDB_STUB_MAX_LINE - 4);
256 } else if (!strncmp("fThreadInfo#", message, 12)) {
257 strncpy(stub->outgoing, "m1", GDB_STUB_MAX_LINE - 4);
258 } else if (!strncmp("sThreadInfo#", message, 12)) {
259 strncpy(stub->outgoing, "l", GDB_STUB_MAX_LINE - 4);
260 }
261 _sendMessage(stub);
262}
263
264static void _processQWriteCommand(struct GDBStub* stub, const char* message) {
265 stub->outgoing[0] = '\0';
266 if (!strncmp("StartNoAckMode#", message, 16)) {
267 stub->lineAck = GDB_ACK_OFF;
268 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
269 }
270 _sendMessage(stub);
271}
272
273static void _processVWriteCommand(struct GDBStub* stub, const char* message) {
274 (void) (message);
275 stub->outgoing[0] = '\0';
276 _sendMessage(stub);
277}
278
279static void _processVReadCommand(struct GDBStub* stub, const char* message) {
280 stub->outgoing[0] = '\0';
281 if (!strncmp("Attach", message, 6)) {
282 strncpy(stub->outgoing, "1", GDB_STUB_MAX_LINE - 4);
283 ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL);
284 }
285 _sendMessage(stub);
286}
287
288static void _setBreakpoint(struct GDBStub* stub, const char* message) {
289 switch (message[0]) {
290 case '0': // Memory breakpoints are not currently supported
291 case '1': {
292 const char* readAddress = &message[2];
293 unsigned i = 0;
294 uint32_t address = _readHex(readAddress, &i);
295 readAddress += i + 1;
296 uint32_t kind = _readHex(readAddress, &i); // We don't use this in hardware watchpoints
297 (void) (kind);
298 ARMDebuggerSetBreakpoint(&stub->d, address);
299 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
300 _sendMessage(stub);
301 break;
302 }
303 case '2':
304 case '3':
305 // TODO: Watchpoints
306 default:
307 break;
308 }
309}
310
311static void _clearBreakpoint(struct GDBStub* stub, const char* message) {
312 switch (message[0]) {
313 case '0': // Memory breakpoints are not currently supported
314 case '1': {
315 const char* readAddress = &message[2];
316 unsigned i = 0;
317 uint32_t address = _readHex(readAddress, &i);
318 ARMDebuggerClearBreakpoint(&stub->d, address);
319 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
320 _sendMessage(stub);
321 break;
322 }
323 case '2':
324 case '3':
325 // TODO: Watchpoints
326 default:
327 break;
328 }
329}
330
331size_t _parseGDBMessage(struct GDBStub* stub, const char* message) {
332 uint8_t checksum = 0;
333 int parsed = 1;
334 switch (*message) {
335 case '+':
336 stub->lineAck = GDB_ACK_RECEIVED;
337 return parsed;
338 case '-':
339 stub->lineAck = GDB_NAK_RECEIVED;
340 return parsed;
341 case '$':
342 ++message;
343 break;
344 case '\x03':
345 ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL);
346 return parsed;
347 default:
348 _nak(stub);
349 return parsed;
350 }
351
352 int i;
353 char messageType = message[0];
354 for (i = 0; message[i] && message[i] != '#'; ++i, ++parsed) {
355 checksum += message[i];
356 }
357 if (!message[i]) {
358 _nak(stub);
359 return parsed;
360 }
361 ++i;
362 ++parsed;
363 if (!message[i]) {
364 _nak(stub);
365 return parsed;
366 } else if (!message[i + 1]) {
367 ++parsed;
368 _nak(stub);
369 return parsed;
370 }
371 parsed += 2;
372 int networkChecksum = _hex2int(&message[i], 2);
373 if (networkChecksum != checksum) {
374 if (stub->d.log) {
375 stub->d.log(&stub->d, DEBUGGER_LOG_WARN, "Checksum error: expected %02x, got %02x", checksum, networkChecksum);
376 }
377 _nak(stub);
378 return parsed;
379 }
380
381 _ack(stub);
382 ++message;
383 switch (messageType) {
384 case '?':
385 snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGINT);
386 _sendMessage(stub);
387 break;
388 case 'c':
389 _continue(stub, message);
390 break;
391 case 'g':
392 _readGPRs(stub, message);
393 break;
394 case 'H':
395 // This is faked because we only have one thread
396 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
397 _sendMessage(stub);
398 break;
399 case 'm':
400 _readMemory(stub, message);
401 break;
402 case 'p':
403 _readRegister(stub, message);
404 break;
405 case 'Q':
406 _processQWriteCommand(stub, message);
407 break;
408 case 'q':
409 _processQReadCommand(stub, message);
410 break;
411 case 's':
412 _step(stub, message);
413 break;
414 case 'V':
415 _processVWriteCommand(stub, message);
416 break;
417 case 'v':
418 _processVReadCommand(stub, message);
419 break;
420 case 'Z':
421 _setBreakpoint(stub, message);
422 break;
423 case 'z':
424 _clearBreakpoint(stub, message);
425 break;
426 default:
427 _error(stub, GDB_UNSUPPORTED_COMMAND);
428 break;
429 }
430 return parsed;
431}
432
433void GDBStubCreate(struct GDBStub* stub) {
434 stub->socket = -1;
435 stub->connection = -1;
436 stub->d.init = 0;
437 stub->d.deinit = _gdbStubDeinit;
438 stub->d.paused = _gdbStubPoll;
439 stub->d.entered = _gdbStubEntered;
440 stub->d.log = 0;
441}
442
443int GDBStubListen(struct GDBStub* stub, int port, uint32_t bindAddress) {
444 if (stub->socket >= 0) {
445 GDBStubShutdown(stub);
446 }
447 // TODO: support IPv6
448 stub->socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
449 if (stub->socket < 0) {
450 if (stub->d.log) {
451 stub->d.log(&stub->d, DEBUGGER_LOG_ERROR, "Couldn't open socket");
452 }
453 return 0;
454 }
455
456 struct sockaddr_in bindInfo = {
457 .sin_family = AF_INET,
458 .sin_port = htons(port),
459 .sin_addr = {
460 .s_addr = htonl(bindAddress)
461 }
462 };
463 int err = bind(stub->socket, (const struct sockaddr*) &bindInfo, sizeof(struct sockaddr_in));
464 if (err) {
465 goto cleanup;
466 }
467 err = listen(stub->socket, 1);
468 if (err) {
469 goto cleanup;
470 }
471 int flags = fcntl(stub->socket, F_GETFL);
472 if (flags == -1) {
473 goto cleanup;
474 }
475 fcntl(stub->socket, F_SETFL, flags | O_NONBLOCK);
476
477 return 1;
478
479cleanup:
480 close(stub->socket);
481 stub->socket = -1;
482 return 0;
483}
484
485void GDBStubHangup(struct GDBStub* stub) {
486 if (stub->connection >= 0) {
487 close(stub->connection);
488 stub->connection = -1;
489 }
490 if (stub->d.state == DEBUGGER_PAUSED) {
491 stub->d.state = DEBUGGER_RUNNING;
492 }
493}
494
495void GDBStubShutdown(struct GDBStub* stub) {
496 GDBStubHangup(stub);
497 if (stub->socket >= 0) {
498 close(stub->socket);
499 stub->socket = -1;
500 }
501}
502
503void GDBStubUpdate(struct GDBStub* stub) {
504 if (stub->connection == -1) {
505 stub->connection = accept(stub->socket, 0, 0);
506 if (stub->connection >= 0) {
507 int flags = fcntl(stub->connection, F_GETFL);
508 if (flags == -1) {
509 goto connectionLost;
510 }
511 fcntl(stub->connection, F_SETFL, flags | O_NONBLOCK);
512 ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_ATTACHED);
513 } else if (errno == EWOULDBLOCK || errno == EAGAIN) {
514 return;
515 } else {
516 goto connectionLost;
517 }
518 }
519 while (1) {
520 ssize_t messageLen = recv(stub->connection, stub->line, GDB_STUB_MAX_LINE - 1, 0);
521 if (messageLen == 0) {
522 goto connectionLost;
523 }
524 if (messageLen == -1) {
525 if (errno == EWOULDBLOCK || errno == EAGAIN) {
526 return;
527 }
528 goto connectionLost;
529 }
530 stub->line[messageLen] = '\0';
531 if (stub->d.log) {
532 stub->d.log(&stub->d, DEBUGGER_LOG_DEBUG, "< %s", stub->line);
533 }
534 ssize_t position = 0;
535 while (position < messageLen) {
536 position += _parseGDBMessage(stub, &stub->line[position]);
537 }
538 }
539
540connectionLost:
541 if (stub->d.log) {
542 stub->d.log(&stub->d, DEBUGGER_LOG_INFO, "Connection lost");
543 }
544 GDBStubHangup(stub);
545}