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