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