mgba-ps3/src/debugger/gdb-stub.c
2015-06-29 20:45:08 -07:00

574 lines
14 KiB
C

/* 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 <signal.h>
#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;
stub->lineAck = GDB_ACK_PENDING;
stub->shouldBlock = false;
}
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);
}