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