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