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 printf("Packet error\n");
75 send(stub->connection, &nak, 1, 0);
76}
77
78static uint32_t _hex2int(const char* hex, int maxDigits) {
79 uint32_t value = 0;
80 uint8_t letter;
81
82 while (maxDigits--) {
83 letter = *hex - '0';
84 if (letter > 9) {
85 letter = *hex - 'a';
86 if (letter > 5) {
87 break;
88 }
89 value *= 0x10;
90 value += letter + 10;
91 } else {
92 value *= 0x10;
93 value += letter;
94 }
95 ++hex;
96 }
97 return value;
98}
99
100static void _int2hex8(uint8_t value, char* out) {
101 static const char language[] = "0123456789abcdef";
102 out[0] = language[value >> 4];
103 out[1] = language[value & 0xF];
104}
105
106static void _int2hex32(uint32_t value, char* out) {
107 static const char language[] = "0123456789abcdef";
108 out[6] = language[value >> 28];
109 out[7] = language[(value >> 24) & 0xF];
110 out[4] = language[(value >> 20) & 0xF];
111 out[5] = language[(value >> 16) & 0xF];
112 out[2] = language[(value >> 12) & 0xF];
113 out[3] = language[(value >> 8) & 0xF];
114 out[0] = language[(value >> 4) & 0xF];
115 out[1] = language[value & 0xF];
116}
117
118static uint32_t _readHex(const char* in, unsigned* out) {
119 unsigned i;
120 for (i = 0; i < 8; ++i) {
121 if (in[i] == ',') {
122 break;
123 }
124 }
125 *out += i;
126 return _hex2int(in, i);
127}
128
129static void _sendMessage(struct GDBStub* stub) {
130 if (stub->lineAck != GDB_ACK_OFF) {
131 stub->lineAck = GDB_ACK_PENDING;
132 }
133 uint8_t checksum = 0;
134 int i = 1;
135 char buffer = stub->outgoing[0];
136 char swap;
137 stub->outgoing[0] = '$';
138 if (buffer) {
139 for (; i < GDB_STUB_MAX_LINE - 5; ++i) {
140 checksum += buffer;
141 swap = stub->outgoing[i];
142 stub->outgoing[i] = buffer;
143 buffer = swap;
144 if (!buffer) {
145 ++i;
146 break;
147 }
148 }
149 }
150 stub->outgoing[i] = '#';
151 _int2hex8(checksum, &stub->outgoing[i + 1]);
152 stub->outgoing[i + 3] = 0;
153 printf("> %s\n", stub->outgoing);
154 send(stub->connection, stub->outgoing, i + 3, 0);
155}
156
157static void _error(struct GDBStub* stub, enum GDBError error) {
158 snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "E%02x", error);
159 _sendMessage(stub);
160}
161
162static void _writeHostInfo(struct GDBStub* stub) {
163 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);
164 _sendMessage(stub);
165}
166
167static void _continue(struct GDBStub* stub, const char* message) {
168 stub->d.state = DEBUGGER_RUNNING;
169 if (stub->connection >= 0) {
170 int flags = fcntl(stub->connection, F_GETFL);
171 if (flags == -1) {
172 GDBStubHangup(stub);
173 return;
174 }
175 fcntl(stub->connection, F_SETFL, flags | O_NONBLOCK);
176 }
177 // TODO: parse message
178}
179
180static void _readMemory(struct GDBStub* stub, const char* message) {
181 const char* readAddress = message;
182 unsigned i = 0;
183 uint32_t address = _readHex(readAddress, &i);
184 readAddress += i + 1;
185 uint32_t size = _readHex(readAddress, &i);
186 if (size > 512) {
187 _error(stub, GDB_BAD_ARGUMENTS);
188 return;
189 }
190 struct ARMMemory* memory = stub->d.memoryShim.original;
191 int writeAddress = 0;
192 for (i = 0; i < size; ++i, writeAddress += 2) {
193 uint8_t byte = memory->load8(memory, address + i, 0);
194 _int2hex8(byte, &stub->outgoing[writeAddress]);
195 }
196 stub->outgoing[writeAddress] = 0;
197 _sendMessage(stub);
198}
199
200static void _readGPRs(struct GDBStub* stub, const char* message) {
201 (void) (message);
202 int r;
203 int i = 0;
204 for (r = 0; r < 16; ++r) {
205 _int2hex32(stub->d.cpu->gprs[r], &stub->outgoing[i]);
206 i += 8;
207 }
208 stub->outgoing[i] = 0;
209 _sendMessage(stub);
210}
211
212static void _readRegister(struct GDBStub* stub, const char* message) {
213 const char* readAddress = message;
214 unsigned i = 0;
215 uint32_t reg = _readHex(readAddress, &i);
216 uint32_t value;
217 if (reg < 0x10) {
218 value = stub->d.cpu->gprs[reg];
219 } else if (reg == 0x19) {
220 value = stub->d.cpu->cpsr.packed;
221 } else {
222 stub->outgoing[0] = '\0';
223 _sendMessage(stub);
224 return;
225 }
226 _int2hex32(value, stub->outgoing);
227 stub->outgoing[8] = '\0';
228 _sendMessage(stub);
229}
230
231static void _processQReadCommand(struct GDBStub* stub, const char* message) {
232 stub->outgoing[0] = '\0';
233 if (!strncmp("HostInfo#", message, 9)) {
234 _writeHostInfo(stub);
235 return;
236 }
237 if (!strncmp("Attached#", message, 9)) {
238 strncpy(stub->outgoing, "1", GDB_STUB_MAX_LINE - 4);
239 } else if (!strncmp("VAttachOrWaitSupported#", message, 23)) {
240 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
241 } else if (!strncmp("C#", message, 2)) {
242 strncpy(stub->outgoing, "QC1", GDB_STUB_MAX_LINE - 4);
243 } else if (!strncmp("fThreadInfo#", message, 12)) {
244 strncpy(stub->outgoing, "m1", GDB_STUB_MAX_LINE - 4);
245 } else if (!strncmp("sThreadInfo#", message, 12)) {
246 strncpy(stub->outgoing, "l", GDB_STUB_MAX_LINE - 4);
247 }
248 _sendMessage(stub);
249}
250
251static void _processQWriteCommand(struct GDBStub* stub, const char* message) {
252 stub->outgoing[0] = '\0';
253 if (!strncmp("StartNoAckMode#", message, 16)) {
254 stub->lineAck = GDB_ACK_OFF;
255 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
256 }
257 _sendMessage(stub);
258}
259
260static void _processVWriteCommand(struct GDBStub* stub, const char* message) {
261 stub->outgoing[0] = '\0';
262 _sendMessage(stub);
263}
264
265static void _processVReadCommand(struct GDBStub* stub, const char* message) {
266 stub->outgoing[0] = '\0';
267 if (!strncmp("Attach", message, 6)) {
268 strncpy(stub->outgoing, "1", GDB_STUB_MAX_LINE - 4);
269 ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL);
270 }
271 _sendMessage(stub);
272}
273
274static void _setBreakpoint(struct GDBStub* stub, const char* message) {
275 switch (message[0]) {
276 case '0': // Memory breakpoints are not currently supported
277 case '1': {
278 const char* readAddress = &message[2];
279 unsigned i = 0;
280 uint32_t address = _readHex(readAddress, &i);
281 readAddress += i + 1;
282 uint32_t kind = _readHex(readAddress, &i); // We don't use this in hardware watchpoints
283 ARMDebuggerSetBreakpoint(&stub->d, address);
284 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
285 _sendMessage(stub);
286 break;
287 }
288 case '2':
289 case '3':
290 // TODO: Watchpoints
291 default:
292 break;
293 }
294}
295
296static void _clearBreakpoint(struct GDBStub* stub, const char* message) {
297 switch (message[0]) {
298 case '0': // Memory breakpoints are not currently supported
299 case '1': {
300 const char* readAddress = &message[2];
301 unsigned i = 0;
302 uint32_t address = _readHex(readAddress, &i);
303 ARMDebuggerClearBreakpoint(&stub->d, address);
304 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
305 _sendMessage(stub);
306 break;
307 }
308 case '2':
309 case '3':
310 // TODO: Watchpoints
311 default:
312 break;
313 }
314}
315
316size_t _parseGDBMessage(struct GDBStub* stub, const char* message) {
317 uint8_t checksum = 0;
318 int parsed = 1;
319 printf("< %s\n", stub->line);
320 switch (*message) {
321 case '+':
322 stub->lineAck = GDB_ACK_RECEIVED;
323 return parsed;
324 case '-':
325 stub->lineAck = GDB_NAK_RECEIVED;
326 return parsed;
327 case '$':
328 ++message;
329 break;
330 case '\x03':
331 ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL);
332 return parsed;
333 default:
334 _nak(stub);
335 return parsed;
336 }
337
338 int i;
339 char messageType = message[0];
340 for (i = 0; message[i] && message[i] != '#'; ++i, ++parsed) {
341 checksum += message[i];
342 }
343 if (!message[i]) {
344 _nak(stub);
345 return parsed;
346 }
347 ++i;
348 ++parsed;
349 if (!message[i]) {
350 _nak(stub);
351 return parsed;
352 } else if (!message[i + 1]) {
353 ++parsed;
354 _nak(stub);
355 return parsed;
356 }
357 parsed += 2;
358 int networkChecksum = _hex2int(&message[i], 2);
359 if (networkChecksum != checksum) {
360 printf("Checksum error: expected %02x, got %02x\n", checksum, networkChecksum);
361 _nak(stub);
362 return parsed;
363 }
364
365 _ack(stub);
366 ++message;
367 switch (messageType) {
368 case '?':
369 snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGINT);
370 _sendMessage(stub);
371 break;
372 case 'c':
373 _continue(stub, message);
374 break;
375 case 'g':
376 _readGPRs(stub, message);
377 break;
378 case 'H':
379 // This is faked because we only have one thread
380 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
381 _sendMessage(stub);
382 break;
383 case 'm':
384 _readMemory(stub, message);
385 break;
386 case 'p':
387 _readRegister(stub, message);
388 break;
389 case 'Q':
390 _processQWriteCommand(stub, message);
391 break;
392 case 'q':
393 _processQReadCommand(stub, message);
394 break;
395 case 'V':
396 _processVWriteCommand(stub, message);
397 break;
398 case 'v':
399 _processVReadCommand(stub, message);
400 break;
401 case 'Z':
402 _setBreakpoint(stub, message);
403 break;
404 case 'z':
405 _clearBreakpoint(stub, message);
406 break;
407 default:
408 _error(stub, GDB_UNSUPPORTED_COMMAND);
409 break;
410 }
411 return parsed;
412}
413
414void GDBStubCreate(struct GDBStub* stub) {
415 stub->socket = -1;
416 stub->connection = -1;
417 stub->d.init = 0;
418 stub->d.deinit = _gdbStubDeinit;
419 stub->d.paused = _gdbStubPoll;
420 stub->d.entered = _gdbStubEntered;
421}
422
423int GDBStubListen(struct GDBStub* stub, int port, uint32_t bindAddress) {
424 if (stub->socket >= 0) {
425 GDBStubShutdown(stub);
426 }
427 // TODO: support IPv6
428 stub->socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
429 if (stub->socket < 0) {
430 printf("Couldn't open socket\n");
431 return 0;
432 }
433
434 struct sockaddr_in bindInfo = {
435 .sin_family = AF_INET,
436 .sin_port = htons(port),
437 .sin_addr = {
438 .s_addr = htonl(bindAddress)
439 }
440 };
441 int err = bind(stub->socket, (const struct sockaddr*) &bindInfo, sizeof(struct sockaddr_in));
442 if (err) {
443 goto cleanup;
444 }
445 err = listen(stub->socket, 1);
446 if (err) {
447 goto cleanup;
448 }
449 int flags = fcntl(stub->socket, F_GETFL);
450 if (flags == -1) {
451 goto cleanup;
452 }
453 fcntl(stub->socket, F_SETFL, flags | O_NONBLOCK);
454
455 return 1;
456
457cleanup:
458 close(stub->socket);
459 stub->socket = -1;
460 return 0;
461}
462
463void GDBStubHangup(struct GDBStub* stub) {
464 if (stub->connection >= 0) {
465 close(stub->connection);
466 stub->connection = -1;
467 }
468 if (stub->d.state == DEBUGGER_PAUSED) {
469 stub->d.state = DEBUGGER_RUNNING;
470 }
471}
472
473void GDBStubShutdown(struct GDBStub* stub) {
474 GDBStubHangup(stub);
475 if (stub->socket >= 0) {
476 close(stub->socket);
477 stub->socket = -1;
478 }
479}
480
481void GDBStubUpdate(struct GDBStub* stub) {
482 if (stub->connection == -1) {
483 stub->connection = accept(stub->socket, 0, 0);
484 if (stub->connection >= 0) {
485 int flags = fcntl(stub->connection, F_GETFL);
486 if (flags == -1) {
487 goto connectionLost;
488 }
489 fcntl(stub->connection, F_SETFL, flags | O_NONBLOCK);
490 ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_ATTACHED);
491 } else if (errno == EWOULDBLOCK || errno == EAGAIN) {
492 return;
493 } else {
494 goto connectionLost;
495 }
496 }
497 while (1) {
498 ssize_t messageLen = recv(stub->connection, stub->line, GDB_STUB_MAX_LINE - 1, 0);
499 if (messageLen == 0) {
500 goto connectionLost;
501 }
502 if (messageLen == -1) {
503 if (errno == EWOULDBLOCK || errno == EAGAIN) {
504 return;
505 }
506 goto connectionLost;
507 }
508 stub->line[messageLen] = '\0';
509 ssize_t position = 0;
510 while (position < messageLen) {
511 position += _parseGDBMessage(stub, &stub->line[position]);
512 }
513 }
514
515connectionLost:
516 // TODO: add logging support to the debugging interface
517 printf("Connection lost\n");
518 GDBStubHangup(stub);
519}