/* Copyright (c) 2013-2014 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "gdb-stub.h" #include #ifndef SIGTRAP #define SIGTRAP 5 /* Win32 Signals do not include SIGTRAP */ #endif #define SOCKET_TIMEOUT 50 enum GDBError { GDB_NO_ERROR = 0x00, GDB_BAD_ARGUMENTS = 0x06, GDB_UNSUPPORTED_COMMAND = 0x07 }; enum { MACH_O_ARM = 12, MACH_O_ARM_V4T = 5 }; static void _sendMessage(struct GDBStub* stub); static void _gdbStubDeinit(struct ARMDebugger* debugger) { struct GDBStub* stub = (struct GDBStub*) debugger; if (!SOCKET_FAILED(stub->socket)) { GDBStubShutdown(stub); } } static void _gdbStubEntered(struct ARMDebugger* debugger, enum DebuggerEntryReason reason, struct DebuggerEntryInfo* info) { struct GDBStub* stub = (struct GDBStub*) debugger; switch (reason) { case DEBUGGER_ENTER_MANUAL: snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGINT); break; case DEBUGGER_ENTER_BREAKPOINT: snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP); break; case DEBUGGER_ENTER_WATCHPOINT: // TODO: Make watchpoints raise with address if (info) { const char* type = 0; switch (info->watchType) { case WATCHPOINT_WRITE: type = "watch"; break; case WATCHPOINT_READ: type = "rwatch"; break; case WATCHPOINT_RW: type = "awatch"; break; } snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "T%02x%s:%08X", SIGTRAP, type, info->address); } else { snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP); } break; case DEBUGGER_ENTER_ILLEGAL_OP: snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGILL); break; case DEBUGGER_ENTER_ATTACHED: return; } _sendMessage(stub); } static void _gdbStubPoll(struct ARMDebugger* debugger) { struct GDBStub* stub = (struct GDBStub*) debugger; --stub->untilPoll; if (stub->untilPoll > 0) { return; } stub->untilPoll = GDB_STUB_INTERVAL; stub->shouldBlock = false; GDBStubUpdate(stub); } static void _gdbStubWait(struct ARMDebugger* debugger) { struct GDBStub* stub = (struct GDBStub*) debugger; stub->shouldBlock = true; GDBStubUpdate(stub); } static void _ack(struct GDBStub* stub) { char ack = '+'; SocketSend(stub->connection, &ack, 1); } static void _nak(struct GDBStub* stub) { char nak = '-'; if (stub->d.log) { stub->d.log(&stub->d, DEBUGGER_LOG_WARN, "Packet error"); } SocketSend(stub->connection, &nak, 1); } static uint32_t _hex2int(const char* hex, int maxDigits) { uint32_t value = 0; uint8_t letter; while (maxDigits--) { letter = *hex - '0'; if (letter > 9) { letter = *hex - 'a'; if (letter > 5) { break; } value *= 0x10; value += letter + 10; } else { value *= 0x10; value += letter; } ++hex; } return value; } static void _int2hex8(uint8_t value, char* out) { static const char language[] = "0123456789abcdef"; out[0] = language[value >> 4]; out[1] = language[value & 0xF]; } static void _int2hex32(uint32_t value, char* out) { static const char language[] = "0123456789abcdef"; out[6] = language[value >> 28]; out[7] = language[(value >> 24) & 0xF]; out[4] = language[(value >> 20) & 0xF]; out[5] = language[(value >> 16) & 0xF]; out[2] = language[(value >> 12) & 0xF]; out[3] = language[(value >> 8) & 0xF]; out[0] = language[(value >> 4) & 0xF]; out[1] = language[value & 0xF]; } static uint32_t _readHex(const char* in, unsigned* out) { unsigned i; for (i = 0; i < 8; ++i) { if (in[i] == ',') { break; } } *out += i; return _hex2int(in, i); } static void _sendMessage(struct GDBStub* stub) { if (stub->lineAck != GDB_ACK_OFF) { stub->lineAck = GDB_ACK_PENDING; } uint8_t checksum = 0; int i = 1; char buffer = stub->outgoing[0]; char swap; stub->outgoing[0] = '$'; if (buffer) { for (; i < GDB_STUB_MAX_LINE - 5; ++i) { checksum += buffer; swap = stub->outgoing[i]; stub->outgoing[i] = buffer; buffer = swap; if (!buffer) { ++i; break; } } } stub->outgoing[i] = '#'; _int2hex8(checksum, &stub->outgoing[i + 1]); stub->outgoing[i + 3] = 0; if (stub->d.log) { stub->d.log(&stub->d, DEBUGGER_LOG_DEBUG, "> %s", stub->outgoing); } SocketSend(stub->connection, stub->outgoing, i + 3); } static void _error(struct GDBStub* stub, enum GDBError error) { snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "E%02x", error); _sendMessage(stub); } static void _writeHostInfo(struct GDBStub* stub) { 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); _sendMessage(stub); } static void _continue(struct GDBStub* stub, const char* message) { stub->d.state = DEBUGGER_CUSTOM; stub->untilPoll = GDB_STUB_INTERVAL; // TODO: parse message UNUSED(message); } static void _step(struct GDBStub* stub, const char* message) { ARMRun(stub->d.cpu); snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP); _sendMessage(stub); // TODO: parse message UNUSED(message); } static void _readMemory(struct GDBStub* stub, const char* message) { const char* readAddress = message; unsigned i = 0; uint32_t address = _readHex(readAddress, &i); readAddress += i + 1; uint32_t size = _readHex(readAddress, &i); if (size > 512) { _error(stub, GDB_BAD_ARGUMENTS); return; } struct ARMCore* cpu = stub->d.cpu; int writeAddress = 0; for (i = 0; i < size; ++i, writeAddress += 2) { uint8_t byte = cpu->memory.load8(cpu, address + i, 0); _int2hex8(byte, &stub->outgoing[writeAddress]); } stub->outgoing[writeAddress] = 0; _sendMessage(stub); } static void _readGPRs(struct GDBStub* stub, const char* message) { UNUSED(message); int r; int i = 0; for (r = 0; r < 16; ++r) { _int2hex32(stub->d.cpu->gprs[r], &stub->outgoing[i]); i += 8; } stub->outgoing[i] = 0; _sendMessage(stub); } static void _readRegister(struct GDBStub* stub, const char* message) { const char* readAddress = message; unsigned i = 0; uint32_t reg = _readHex(readAddress, &i); uint32_t value; if (reg < 0x10) { value = stub->d.cpu->gprs[reg]; } else if (reg == 0x19) { value = stub->d.cpu->cpsr.packed; } else { stub->outgoing[0] = '\0'; _sendMessage(stub); return; } _int2hex32(value, stub->outgoing); stub->outgoing[8] = '\0'; _sendMessage(stub); } static void _processQReadCommand(struct GDBStub* stub, const char* message) { stub->outgoing[0] = '\0'; if (!strncmp("HostInfo#", message, 9)) { _writeHostInfo(stub); return; } if (!strncmp("Attached#", message, 9)) { strncpy(stub->outgoing, "1", GDB_STUB_MAX_LINE - 4); } else if (!strncmp("VAttachOrWaitSupported#", message, 23)) { strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4); } else if (!strncmp("C#", message, 2)) { strncpy(stub->outgoing, "QC1", GDB_STUB_MAX_LINE - 4); } else if (!strncmp("fThreadInfo#", message, 12)) { strncpy(stub->outgoing, "m1", GDB_STUB_MAX_LINE - 4); } else if (!strncmp("sThreadInfo#", message, 12)) { strncpy(stub->outgoing, "l", GDB_STUB_MAX_LINE - 4); } _sendMessage(stub); } static void _processQWriteCommand(struct GDBStub* stub, const char* message) { stub->outgoing[0] = '\0'; if (!strncmp("StartNoAckMode#", message, 16)) { stub->lineAck = GDB_ACK_OFF; strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4); } _sendMessage(stub); } static void _processVWriteCommand(struct GDBStub* stub, const char* message) { UNUSED(message); stub->outgoing[0] = '\0'; _sendMessage(stub); } static void _processVReadCommand(struct GDBStub* stub, const char* message) { stub->outgoing[0] = '\0'; if (!strncmp("Attach", message, 6)) { strncpy(stub->outgoing, "1", GDB_STUB_MAX_LINE - 4); ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL, 0); } _sendMessage(stub); } static void _setBreakpoint(struct GDBStub* stub, const char* message) { const char* readAddress = &message[2]; unsigned i = 0; uint32_t address = _readHex(readAddress, &i); readAddress += i + 1; uint32_t kind = _readHex(readAddress, &i); // We don't use this in hardware watchpoints UNUSED(kind); switch (message[0]) { case '0': // Memory breakpoints are not currently supported case '1': ARMDebuggerSetBreakpoint(&stub->d, address); strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4); _sendMessage(stub); break; case '2': case '3': case '4': ARMDebuggerSetWatchpoint(&stub->d, address); strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4); _sendMessage(stub); break; default: stub->outgoing[0] = '\0'; _sendMessage(stub); break; } } static void _clearBreakpoint(struct GDBStub* stub, const char* message) { const char* readAddress = &message[2]; unsigned i = 0; uint32_t address = _readHex(readAddress, &i); switch (message[0]) { case '0': // Memory breakpoints are not currently supported case '1': ARMDebuggerClearBreakpoint(&stub->d, address); break; case '2': case '3': case '4': ARMDebuggerClearWatchpoint(&stub->d, address); break; default: break; } strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4); _sendMessage(stub); } size_t _parseGDBMessage(struct GDBStub* stub, const char* message) { uint8_t checksum = 0; int parsed = 1; switch (*message) { case '+': stub->lineAck = GDB_ACK_RECEIVED; return parsed; case '-': stub->lineAck = GDB_NAK_RECEIVED; return parsed; case '$': ++message; break; case '\x03': ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL, 0); return parsed; default: _nak(stub); return parsed; } int i; char messageType = message[0]; for (i = 0; message[i] && message[i] != '#'; ++i, ++parsed) { checksum += message[i]; } if (!message[i]) { _nak(stub); return parsed; } ++i; ++parsed; if (!message[i]) { _nak(stub); return parsed; } else if (!message[i + 1]) { ++parsed; _nak(stub); return parsed; } parsed += 2; int networkChecksum = _hex2int(&message[i], 2); if (networkChecksum != checksum) { if (stub->d.log) { stub->d.log(&stub->d, DEBUGGER_LOG_WARN, "Checksum error: expected %02x, got %02x", checksum, networkChecksum); } _nak(stub); return parsed; } _ack(stub); ++message; switch (messageType) { case '?': snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGINT); _sendMessage(stub); break; case 'c': _continue(stub, message); break; case 'g': _readGPRs(stub, message); break; case 'H': // This is faked because we only have one thread strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4); _sendMessage(stub); break; case 'm': _readMemory(stub, message); break; case 'p': _readRegister(stub, message); break; case 'Q': _processQWriteCommand(stub, message); break; case 'q': _processQReadCommand(stub, message); break; case 's': _step(stub, message); break; case 'V': _processVWriteCommand(stub, message); break; case 'v': _processVReadCommand(stub, message); break; case 'Z': _setBreakpoint(stub, message); break; case 'z': _clearBreakpoint(stub, message); break; default: _error(stub, GDB_UNSUPPORTED_COMMAND); break; } return parsed; } void GDBStubCreate(struct GDBStub* stub) { ARMDebuggerCreate(&stub->d); stub->socket = INVALID_SOCKET; stub->connection = INVALID_SOCKET; stub->d.init = 0; stub->d.deinit = _gdbStubDeinit; stub->d.paused = _gdbStubWait; stub->d.entered = _gdbStubEntered; stub->d.custom = _gdbStubPoll; stub->d.log = 0; stub->untilPoll = GDB_STUB_INTERVAL; } bool GDBStubListen(struct GDBStub* stub, int port, const struct Address* bindAddress) { if (!SOCKET_FAILED(stub->socket)) { GDBStubShutdown(stub); } stub->socket = SocketOpenTCP(port, bindAddress); if (SOCKET_FAILED(stub->socket)) { if (stub->d.log) { stub->d.log(&stub->d, DEBUGGER_LOG_ERROR, "Couldn't open socket"); } return false; } if (!SocketSetBlocking(stub->socket, false)) { goto cleanup; } int err = SocketListen(stub->socket, 1); if (err) { goto cleanup; } return true; cleanup: if (stub->d.log) { stub->d.log(&stub->d, DEBUGGER_LOG_ERROR, "Couldn't listen on port"); } SocketClose(stub->socket); stub->socket = INVALID_SOCKET; return false; } void GDBStubHangup(struct GDBStub* stub) { if (!SOCKET_FAILED(stub->connection)) { SocketClose(stub->connection); stub->connection = INVALID_SOCKET; } if (stub->d.state == DEBUGGER_PAUSED) { stub->d.state = DEBUGGER_RUNNING; } } void GDBStubShutdown(struct GDBStub* stub) { GDBStubHangup(stub); if (!SOCKET_FAILED(stub->socket)) { SocketClose(stub->socket); stub->socket = INVALID_SOCKET; } } void GDBStubUpdate(struct GDBStub* stub) { if (stub->socket == INVALID_SOCKET) { if (stub->d.state == DEBUGGER_PAUSED) { stub->d.state = DEBUGGER_RUNNING; } return; } if (stub->connection == INVALID_SOCKET) { if (stub->shouldBlock) { Socket reads = stub->socket; SocketPoll(1, &reads, 0, 0, SOCKET_TIMEOUT); } stub->connection = SocketAccept(stub->socket, 0); if (!SOCKET_FAILED(stub->connection)) { if (!SocketSetBlocking(stub->connection, false)) { goto connectionLost; } ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_ATTACHED, 0); } else if (SocketWouldBlock()) { return; } else { goto connectionLost; } } while (true) { if (stub->shouldBlock) { Socket reads = stub->connection; SocketPoll(1, &reads, 0, 0, SOCKET_TIMEOUT); } ssize_t messageLen = SocketRecv(stub->connection, stub->line, GDB_STUB_MAX_LINE - 1); if (messageLen == 0) { goto connectionLost; } if (messageLen == -1) { if (SocketWouldBlock()) { return; } goto connectionLost; } stub->line[messageLen] = '\0'; if (stub->d.log) { stub->d.log(&stub->d, DEBUGGER_LOG_DEBUG, "< %s", stub->line); } ssize_t position = 0; while (position < messageLen) { position += _parseGDBMessage(stub, &stub->line[position]); } } connectionLost: if (stub->d.log) { stub->d.log(&stub->d, DEBUGGER_LOG_INFO, "Connection lost"); } GDBStubHangup(stub); }