blob: 0c5b3fbac322acb5e676d89ae46132f3c89463d2 [file] [edit]
/* -*- Mode: C++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
#define REVERSE_EXECUTION
/**
* Much of this implementation is based on the documentation at
*
* http://sourceware.org/gdb/onlinedocs/gdb/Packets.html
*/
#include "GdbConnection.h"
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <iomanip>
#include <sstream>
#include <vector>
#include "GdbCommandHandler.h"
#include "ReplaySession.h"
#include "ScopedFd.h"
#include "core.h"
#include "log.h"
using namespace std;
namespace rr {
static const char INTERRUPT_CHAR = '\x03';
#define UNHANDLED_REQ() \
write_packet(""); \
LOG(info)
const GdbThreadId GdbThreadId::ANY(0, 0);
const GdbThreadId GdbThreadId::ALL(-1, -1);
#ifdef DEBUG
static bool request_needs_immediate_response(const GdbRequest* req) {
switch (req->type) {
case DREQ_NONE:
case DREQ_CONT:
return false;
default:
return true;
}
}
#endif
GdbConnection::GdbConnection(pid_t tgid, const Features& features)
: tgid(tgid),
cpu_features_(0),
no_ack(false),
features_(features),
connection_alive_(true) {
#ifndef REVERSE_EXECUTION
features_.reverse_execution = false;
#endif
}
void GdbConnection::await_debugger(ScopedFd& listen_fd) {
sock_fd = ScopedFd(accept(listen_fd, nullptr, nullptr));
// We might restart this debugging session, so don't set the
// socket fd CLOEXEC.
}
/**
* Poll for data to or from gdb, waiting |timeoutMs|. 0 means "don't
* wait", and -1 means "wait forever". Return true if data is ready.
*/
static bool poll_socket(const ScopedFd& sock_fd, short events, int timeoutMs) {
struct pollfd pfd;
memset(&pfd, 0, sizeof(pfd));
pfd.fd = sock_fd;
pfd.events = events;
int ret = poll(&pfd, 1, timeoutMs);
if (ret < 0 && errno != EINTR) {
LOG(info) << "gdb socket has been closed";
}
return ret > 0;
}
static bool poll_incoming(const ScopedFd& sock_fd, int timeoutMs) {
return poll_socket(sock_fd, POLLIN /* TODO: |POLLERR */, timeoutMs);
}
static void poll_outgoing(const ScopedFd& sock_fd, int timeoutMs) {
poll_socket(sock_fd, POLLOUT /* TODO: |POLLERR */, timeoutMs);
}
/**
* read() incoming data exactly one time, successfully. May block.
*/
void GdbConnection::read_data_once() {
ssize_t nread;
/* Wait until there's data, instead of busy-looping on
* EAGAIN. */
poll_incoming(sock_fd, -1 /* wait forever */);
uint8_t buf[4096];
nread = read(sock_fd, buf, sizeof(buf));
if (nread <= 0) {
LOG(info) << "Could not read data from gdb socket, "
"marking connection as closed";
connection_alive_ = false;
} else {
inbuf.insert(inbuf.end(), buf, buf + nread);
}
}
void GdbConnection::write_flush() {
size_t write_index = 0;
outbuf.push_back(0);
LOG(debug) << "write_flush: '" << outbuf.data() << "'";
outbuf.pop_back();
while (write_index < outbuf.size()) {
ssize_t nwritten;
poll_outgoing(sock_fd, -1 /*wait forever*/);
nwritten = write(sock_fd, outbuf.data() + write_index,
outbuf.size() - write_index);
if (nwritten < 0) {
LOG(info) << "Could not write data to gdb socket, "
"marking connection as closed";
connection_alive_ = false;
outbuf.clear();
return;
} else {
write_index += nwritten;
}
}
outbuf.clear();
}
void GdbConnection::write_data_raw(const uint8_t* data, ssize_t len) {
outbuf.insert(outbuf.end(), data, data + len);
}
void GdbConnection::write_hex(unsigned long hex) {
char buf[32];
size_t len;
len = snprintf(buf, sizeof(buf) - 1, "%02lx", hex);
write_data_raw((uint8_t*)buf, len);
}
void GdbConnection::write_packet_bytes(const uint8_t* data, size_t num_bytes) {
uint8_t checksum;
size_t i;
write_data_raw((uint8_t*)"$", 1);
for (i = 0, checksum = 0; i < num_bytes; ++i) {
checksum += data[i];
}
write_data_raw((uint8_t*)data, num_bytes);
write_data_raw((uint8_t*)"#", 1);
write_hex(checksum);
}
void GdbConnection::write_packet(const char* data) {
return write_packet_bytes((const uint8_t*)data, strlen(data));
}
void GdbConnection::write_binary_packet(const char* pfx, const uint8_t* data,
ssize_t num_bytes) {
ssize_t pfx_num_chars = strlen(pfx);
vector<uint8_t> buf;
buf.resize(2 * num_bytes + pfx_num_chars);
ssize_t buf_num_bytes = 0;
int i;
memcpy((char*)buf.data(), pfx, pfx_num_chars);
buf_num_bytes += pfx_num_chars;
for (i = 0; i < num_bytes; ++i) {
uint8_t b = data[i];
if (buf_num_bytes + 2 > ssize_t(buf.size())) {
break;
}
switch (b) {
case '#':
case '$':
case '}':
case '*':
buf.data()[buf_num_bytes++] = '}';
buf.data()[buf_num_bytes++] = b ^ 0x20;
break;
default:
buf.data()[buf_num_bytes++] = b;
break;
}
}
LOG(debug) << " ***** NOTE: writing binary data, upcoming debug output may "
"be truncated";
return write_packet_bytes(buf.data(), buf_num_bytes);
}
void GdbConnection::write_hex_bytes_packet(const char* prefix,
const uint8_t* bytes, size_t len) {
if (prefix[0] == '\0' && 0 == len) {
write_packet("");
return;
}
ssize_t pfx_num_chars = strlen(prefix);
vector<char> buf;
buf.resize(pfx_num_chars + 2 * len + 1);
memcpy(buf.data(), prefix, pfx_num_chars);
for (size_t i = 0; i < len; ++i) {
unsigned long b = bytes[i];
snprintf(&buf.data()[pfx_num_chars + 2 * i], 3, "%02lx", b);
}
write_packet(buf.data());
}
void GdbConnection::write_hex_bytes_packet(const uint8_t* bytes, size_t len) {
write_hex_bytes_packet("", bytes, len);
}
static void parser_assert(bool cond) {
if (!cond) {
fputs("Failed to parse gdb request\n", stderr);
DEBUG_ASSERT(false);
exit(2);
}
}
static string decode_ascii_encoded_hex_str(const char* encoded) {
ssize_t enc_len = strlen(encoded);
parser_assert(enc_len % 2 == 0);
string str;
for (int i = 0; i < enc_len / 2; ++i) {
char enc_byte[] = { encoded[2 * i], encoded[2 * i + 1], '\0' };
char* endp;
int c = strtol(enc_byte, &endp, 16);
parser_assert(c < 128);
str += static_cast<char>(c);
}
return str;
}
bool GdbConnection::skip_to_packet_start() {
ssize_t end = -1;
/* XXX we want memcspn() here ... */
for (size_t i = 0; i < inbuf.size(); ++i) {
if (inbuf[i] == '$' || inbuf[i] == INTERRUPT_CHAR) {
end = i;
break;
}
}
if (end < 0) {
/* Discard all read bytes, which we don't care
* about. */
inbuf.clear();
return false;
}
/* Discard bytes up to start-of-packet. */
inbuf.erase(inbuf.begin(), inbuf.begin() + end);
parser_assert(1 <= inbuf.size());
parser_assert('$' == inbuf[0] || INTERRUPT_CHAR == inbuf[0]);
return true;
}
bool GdbConnection::sniff_packet() {
if (skip_to_packet_start()) {
/* We've already seen a (possibly partial) packet. */
return true;
}
parser_assert(inbuf.empty());
return poll_incoming(sock_fd, 0 /*don't wait*/);
}
void GdbConnection::read_packet() {
/* Read and discard bytes until we see the start of a
* packet.
*
* NB: we're ignoring "+/-" responses from gdb. There doesn't
* seem to be any sane reason why we would send a damaged
* packet to gdb over TCP, then see a "-" reply from gdb and
* somehow magically fix our bug that led to the malformed
* packet in the first place.
*/
while (!skip_to_packet_start() && connection_alive_) {
read_data_once();
}
if (!connection_alive_) {
return;
}
if (inbuf[0] == INTERRUPT_CHAR) {
/* Interrupts are kind of an ugly duckling in the gdb
* protocol ... */
packetend = 1;
return;
}
/* Read until we see end-of-packet. */
size_t checkedlen = 0;
while (true) {
uint8_t* p = (uint8_t*)memchr(inbuf.data() + checkedlen, '#',
inbuf.size() - checkedlen);
if (p) {
packetend = p - inbuf.data();
break;
}
checkedlen = inbuf.size();
read_data_once();
if (!connection_alive_) {
return;
}
}
/* NB: we're ignoring the gdb packet checksums here too. If
* gdb is corrupted enough to garble a checksum over TCP, it's
* not really clear why asking for the packet again might make
* the bug go away. */
parser_assert('$' == inbuf[0] && packetend < inbuf.size());
/* Acknowledge receipt of the packet. */
if (!no_ack) {
write_data_raw((uint8_t*)"+", 1);
write_flush();
}
}
static void read_binary_data(const uint8_t* payload, const uint8_t* payload_end,
vector<uint8_t>& data) {
data.clear();
while (payload < payload_end) {
uint8_t b = *payload++;
if ('}' == b) {
parser_assert(payload < payload_end);
b = 0x20 ^ *payload++;
}
data.push_back(b);
}
}
/**
* Parse and return a gdb thread-id from |str|. |endptr| points to
* the character just after the last character in the thread-id. It
* may be nullptr.
*/
static GdbThreadId parse_threadid(const char* str, char** endptr) {
GdbThreadId t;
char* endp;
bool multiprocess = false;
if ('p' == *str) {
multiprocess = true;
++str;
}
t.pid = strtol(str, &endp, 16);
parser_assert(endp);
/* terminators (single process, no PID or TID, depending on 'p' prefix) */
if (*endp == '\0' || *endp == ';' || *endp == ',') {
if (multiprocess) {
t.tid = -1;
} else {
t.tid = t.pid;
t.pid = -1;
}
/* multiprocess syntax "<pid>.<tid>" */
} else if (*endp == '.') {
str = endp + 1;
t.tid = strtol(str, &endp, 16);
}
parser_assert(*endp == '\0' || *endp == ';' || *endp == ',');
*endptr = endp;
return t;
}
void GdbConnection::write_xfer_response(const void* data, size_t size,
uint64_t offset, uint64_t len) {
if (offset > size) {
write_packet("E01");
return;
}
if (offset == size) {
write_packet("l");
return;
}
if (offset + len < size) {
write_binary_packet("m", static_cast<const uint8_t*>(data) + offset, len);
return;
}
write_binary_packet("l", static_cast<const uint8_t*>(data) + offset,
size - offset);
}
static string read_target_desc(const char* file_name) {
#ifdef __BIONIC__
const char* share_path = "usr/share/rr/";
#else
const char* share_path = "share/rr/";
#endif
string path = resource_path() + share_path + string(file_name);
stringstream ss;
FILE* f = fopen(path.c_str(), "r");
if (f == NULL) {
FATAL() << "Failed to load target description file: " << file_name;
}
while (true) {
int ch = getc(f);
if (ch == EOF) {
break;
}
ss << (char)ch;
}
fclose(f);
return ss.str();
}
static const char* target_description_name(uint32_t cpu_features) {
// This doesn't scale, but it's what gdb does...
switch (cpu_features) {
case 0:
return "i386-linux.xml";
case GdbConnection::CPU_X86_64:
return "amd64-linux.xml";
case GdbConnection::CPU_AVX:
return "i386-avx-linux.xml";
case GdbConnection::CPU_X86_64 | GdbConnection::CPU_AVX:
return "amd64-avx-linux.xml";
case GdbConnection::CPU_PKU | GdbConnection::CPU_AVX:
return "i386-pkeys-linux.xml";
case GdbConnection::CPU_X86_64 | GdbConnection::CPU_PKU | GdbConnection::CPU_AVX:
return "amd64-pkeys-linux.xml";
case GdbConnection::CPU_AARCH64:
return "aarch64-core.xml";
default:
FATAL() << "Unknown features";
return nullptr;
}
}
bool GdbConnection::xfer(const char* name, char* args) {
const char* mode = args;
args = strchr(args, ':');
parser_assert(args);
*args++ = '\0';
if (strcmp(mode, "read") && strcmp(mode, "write")) {
write_packet("");
return false;
}
const char* annex = args;
args = strchr(args, ':');
parser_assert(args);
*args++ = '\0';
uint64_t offset = strtoul(args, &args, 16);
uint64_t len = 0;
if (!strcmp(mode, "read")) {
parser_assert(',' == *args++);
len = strtoul(args, &args, 16);
parser_assert(!*args);
} else {
parser_assert(*args == ':');
++args;
}
LOG(debug) << "gdb asks us to transfer " << name << " mode=" << mode
<< ", annex=" << annex << ", offset=" << offset << " len=" << len;
if (!strcmp(name, "auxv")) {
if (strcmp(annex, "")) {
write_packet("E00");
return false;
}
if (strcmp(mode, "read")) {
write_packet("");
return false;
}
req = GdbRequest(DREQ_GET_AUXV);
req.target = query_thread;
// XXX handle offset/len here!
return true;
}
if (!strcmp(name, "exec-file")) {
if (strcmp(mode, "read")) {
write_packet("");
return false;
}
req = GdbRequest(DREQ_GET_EXEC_FILE);
req.target.pid = req.target.tid = strtoul(annex, nullptr, 16);
// XXX handle offset/len here!
return true;
}
if (!strcmp(name, "siginfo")) {
if (strcmp(annex, "")) {
write_packet("E00");
return false;
}
if (!strcmp(mode, "read")) {
req = GdbRequest(DREQ_READ_SIGINFO);
req.target = query_thread;
req.mem().addr = offset;
req.mem().len = len;
return true;
}
req = GdbRequest(DREQ_WRITE_SIGINFO);
req.target = query_thread;
return true;
}
if (!strcmp(name, "features")) {
if (strcmp(mode, "read")) {
write_packet("");
return false;
}
string target_desc =
read_target_desc((strcmp(annex, "") && strcmp(annex, "target.xml"))
? annex
: target_description_name(cpu_features_));
write_xfer_response(target_desc.c_str(), target_desc.size(), offset, len);
return false;
}
write_packet("");
return false;
}
/**
* Format |value| into |buf| in the manner gdb expects. |buf| must
* point at a buffer with at least |1 + 2*DBG_MAX_REG_SIZE| bytes
* available. Fewer bytes than that may be written, but |buf| is
* guaranteed to be null-terminated.
*/
static size_t print_reg_value(const GdbRegisterValue& reg, char* buf) {
parser_assert(reg.size <= GdbRegisterValue::MAX_SIZE);
if (reg.defined) {
/* gdb wants the register value in native endianness.
* reg.value read in native endianness is exactly that.
*/
for (size_t i = 0; i < reg.size; ++i) {
snprintf(&buf[2 * i], 3, "%02lx", (unsigned long)reg.value[i]);
}
} else {
for (size_t i = 0; i < reg.size; ++i) {
strcpy(&buf[2 * i], "xx");
}
}
return reg.size * 2;
}
/**
* Read the encoded register value in |strp| into |reg|. |strp| may
* be mutated.
*/
static void read_reg_value(char** strp, GdbRegisterValue* reg) {
char* str = *strp;
if ('x' == str[0]) {
reg->defined = false;
reg->size = 0;
return;
}
reg->defined = true;
reg->size = strlen(str) / 2;
for (size_t i = 0; i < reg->size; ++i) {
char tmp = str[2];
str[2] = '\0';
reg->value[i] = strtoul(str, &str, 16);
parser_assert('\0' == *str);
str[0] = tmp;
}
*strp = str;
}
bool GdbConnection::query(char* payload) {
const char* name;
char* args;
args = strchr(payload, ':');
if (args) {
*args++ = '\0';
}
name = payload;
if (strstr(name, "RRCmd") == name) {
LOG(debug) << "gdb requests rr cmd: " << name;
req = GdbRequest(DREQ_RR_CMD);
req.text_ = args;
return true;
}
if (!strcmp(name, "C")) {
LOG(debug) << "gdb requests current thread ID";
req = GdbRequest(DREQ_GET_CURRENT_THREAD);
return true;
}
if (!strcmp(name, "Attached")) {
LOG(debug) << "gdb asks if this is a new or existing process";
/* Tell gdb this is an existing process; it might be
* (see emergency_debug()). */
write_packet("1");
return false;
}
if (!strcmp(name, "fThreadInfo")) {
LOG(debug) << "gdb asks for thread list";
req = GdbRequest(DREQ_GET_THREAD_LIST);
return true;
}
if (!strcmp(name, "sThreadInfo")) {
write_packet("l"); /* "end of list" */
return false;
}
if (!strcmp(name, "GetTLSAddr")) {
LOG(debug) << "gdb asks for TLS addr";
req = GdbRequest(DREQ_TLS);
req.target = parse_threadid(args, &args);
parser_assert(*args == ',');
++args;
size_t offset = strtoul(args, &args, 16);
parser_assert(*args == ',');
++args;
remote_ptr<void> load_module = strtoul(args, &args, 16);
parser_assert(*args == '\0');
req.tls().offset = offset;
req.tls().load_module = load_module;
return true;
}
if (!strcmp(name, "Offsets")) {
LOG(debug) << "gdb asks for section offsets";
req = GdbRequest(DREQ_GET_OFFSETS);
req.target = query_thread;
return true;
}
if ('P' == name[0]) {
/* The docs say not to use this packet ... */
write_packet("");
return false;
}
if (!strcmp(name, "Supported")) {
/* TODO process these */
LOG(debug) << "gdb supports " << args;
multiprocess_supported_ = strstr(args, "multiprocess+") != nullptr;
hwbreak_supported_ = strstr(args, "hwbreak+") != nullptr;
swbreak_supported_ = strstr(args, "swbreak+") != nullptr;
stringstream supported;
// Encourage gdb to use very large packets since we support any packet size
supported << "PacketSize=1048576"
";QStartNoAckMode+"
";qXfer:features:read+"
";qXfer:auxv:read+"
";qXfer:exec-file:read+"
";qXfer:siginfo:read+"
";qXfer:siginfo:write+"
";multiprocess+"
";hwbreak+"
";swbreak+"
";ConditionalBreakpoints+"
";vContSupported+"
";QPassSignals+";
if (features().reverse_execution) {
supported << ";ReverseContinue+"
";ReverseStep+";
}
write_packet(supported.str().c_str());
return false;
}
if (!strcmp(name, "Symbol")) {
#ifdef PROC_SERVICE_H
LOG(debug) << "gdb is ready for symbol lookups";
const char* colon = strchr(args, ':');
parser_assert(colon != nullptr);
req = GdbRequest(DREQ_QSYMBOL);
if (*args == ':') {
req.sym().has_address = false;
} else {
req.sym().has_address = true;
req.sym().address = strtoul(args, &args, 16);
}
parser_assert(*args == ':');
++args;
req.sym().name = decode_ascii_encoded_hex_str(args);
return true;
#else
LOG(debug) << "gdb is ready for symbol lookups, but we don't support them";
write_packet("");
return false;
#endif
}
if (strstr(name, "ThreadExtraInfo") == name) {
// ThreadExtraInfo is a special snowflake that
// delimits its args with ','.
parser_assert(!args);
args = payload;
args = 1 + strchr(args, ',' /*sic*/);
req = GdbRequest(DREQ_GET_THREAD_EXTRA_INFO);
req.target = parse_threadid(args, &args);
parser_assert('\0' == *args);
return true;
}
if (!strcmp(name, "TStatus")) {
LOG(debug) << "gdb asks for trace status";
/* XXX from the docs, it appears that we should reply
* with "T0" here. But if we do, gdb keeps bothering
* us with trace queries. So pretend we don't know
* what it's talking about. */
write_packet("");
return false;
}
if (!strcmp(name, "Xfer")) {
name = args;
args = strchr(args, ':');
parser_assert(args);
*args++ = '\0';
return xfer(name, args);
}
if (!strcmp(name, "Search")) {
name = args;
args = strchr(args, ':');
if (args) {
*args++ = '\0';
}
if (!strcmp(name, "memory") && args) {
req = GdbRequest(DREQ_SEARCH_MEM);
req.target = query_thread;
req.mem().addr = strtoul(args, &args, 16);
parser_assert(';' == *args++);
req.mem().len = strtoull(args, &args, 16);
parser_assert(';' == *args++);
read_binary_data((const uint8_t*)args, inbuf.data() + packetend,
req.mem().data);
LOG(debug) << "gdb searching memory (addr=" << HEX(req.mem().addr)
<< ", len=" << req.mem().len << ")";
return true;
}
write_packet("");
return false;
}
UNHANDLED_REQ() << "Unhandled gdb query: q" << name;
return false;
}
bool GdbConnection::set_var(char* payload) {
const char* name;
char* args;
args = strchr(payload, ':');
if (args) {
*args++ = '\0';
}
name = payload;
if (!strcmp(name, "StartNoAckMode")) {
write_packet("OK");
no_ack = true;
return false;
}
if (!strncmp(name, "PassSignals", sizeof("PassSignals"))) {
pass_signals.clear();
while (*args != '\0') {
char *next = nullptr;
int sig = std::strtol(args, &next, 16);
parser_assert(next != nullptr);
LOG(debug) << "registered " << sig << " by QPassSignal";
pass_signals.insert(sig);
args = next;
if (*args == '\0') {
break;
}
parser_assert(*args == ';');
args++;
}
write_packet("OK");
return false;
}
UNHANDLED_REQ() << "Unhandled gdb set: Q" << name;
return false;
}
void GdbConnection::consume_request() {
req = GdbRequest();
write_flush();
}
bool GdbConnection::process_bpacket(char* payload) {
if (strcmp(payload, "c") == 0) {
req = GdbRequest(DREQ_CONT);
req.cont().run_direction = RUN_BACKWARD;
req.cont().actions.push_back(GdbContAction(ACTION_CONTINUE, resume_thread));
return true;
} else if (strcmp(payload, "s") == 0) {
req = GdbRequest(DREQ_CONT);
req.cont().run_direction = RUN_BACKWARD;
req.cont().actions.push_back(GdbContAction(ACTION_STEP, resume_thread));
return true;
} else {
UNHANDLED_REQ() << "Unhandled gdb bpacket: b" << payload;
return false;
}
}
static int gdb_open_flags_to_system_flags(int64_t flags) {
int ret;
switch (flags & 3) {
case 0:
ret = O_RDONLY;
break;
case 1:
ret = O_WRONLY;
break;
case 2:
ret = O_RDWR;
break;
default:
parser_assert(false);
return 0;
}
parser_assert(!(flags & ~int64_t(3 | 0x8 | 0x200 | 0x400 | 0x800)));
if (flags & 0x8) {
ret |= O_APPEND;
}
if (flags & 0x200) {
ret |= O_CREAT;
}
if (flags & 0x400) {
ret |= O_TRUNC;
}
if (flags & 0x800) {
ret |= O_EXCL;
}
return ret;
}
bool GdbConnection::process_vpacket(char* payload) {
const char* name;
char* args;
args = strchr(payload, ';');
if (args) {
*args++ = '\0';
}
name = payload;
if (!strcmp("Cont", name)) {
vector<GdbContAction> actions;
bool has_default_action = false;
GdbContAction default_action;
while (args) {
char* cmd = args;
while (*args != ':' && *args != ';') {
if (!*args) {
args = nullptr;
break;
}
++args;
}
bool is_default = true;
GdbThreadId target;
if (args) {
if (*args == ':') {
is_default = false;
*args = '\0';
target = parse_threadid(args + 1, &args);
}
args = strchr(args, ';');
if (args) {
*args = '\0';
++args;
}
}
GdbActionType action;
int signal_to_deliver = 0;
char* endptr = NULL;
switch (cmd[0]) {
case 'C':
action = ACTION_CONTINUE;
signal_to_deliver = strtol(cmd + 1, &endptr, 16);
break;
case 'c':
action = ACTION_CONTINUE;
break;
case 'S':
action = ACTION_STEP;
signal_to_deliver = strtol(cmd + 1, &cmd, 16);
break;
case 's':
action = ACTION_STEP;
break;
default:
UNHANDLED_REQ() << "Unhandled vCont command " << cmd << "(" << args
<< ")";
return false;
}
if (endptr && *endptr) {
UNHANDLED_REQ() << "Unhandled vCont command parameters " << cmd;
return false;
}
if (is_default) {
if (has_default_action) {
UNHANDLED_REQ()
<< "Unhandled vCont command with multiple default actions";
return false;
}
has_default_action = true;
default_action =
GdbContAction(action, GdbThreadId::ALL, signal_to_deliver);
} else {
actions.push_back(GdbContAction(action, target, signal_to_deliver));
}
}
if (has_default_action) {
actions.push_back(default_action);
}
req = GdbRequest(DREQ_CONT);
req.cont().run_direction = RUN_FORWARD;
req.cont().actions = std::move(actions);
return true;
}
if (!strcmp("Cont?", name)) {
LOG(debug) << "gdb queries which continue commands we support";
write_packet("vCont;c;C;s;S;");
return false;
}
if (!strcmp("Kill", name)) {
// We can't kill tracees or replay can diverge. We
// assume that this kill request is being made because
// a "vRun" restart is coming right up. We know how
// to implement vRun, so we'll ignore this one.
LOG(debug) << "gdb asks us to kill tracee(s); ignoring";
write_packet("OK");
return false;
}
if (!strcmp("Run", name)) {
req = GdbRequest(DREQ_RESTART);
const char* filename = args;
args = strchr(args, ';');
if (args) {
*args++ = '\0';
}
if (strlen(filename)) {
FATAL() << "gdb wants us to run the exe image `" << filename
<< "', but we don't support that.";
}
if (!args) {
req.restart().type = RESTART_FROM_PREVIOUS;
return true;
}
const char* arg1 = args;
args = strchr(args, ';');
if (args) {
*args++ = 0;
LOG(debug) << "Ignoring extra parameters " << args;
}
string event_str = decode_ascii_encoded_hex_str(arg1);
char* endp;
if (event_str[0] == 'c') {
int64_t param = strtoll(event_str.c_str() + 1, &endp, 0);
req.restart().type = RESTART_FROM_CHECKPOINT;
req.restart().param_str = event_str.substr(1);
req.restart().param = param;
LOG(debug) << "next replayer restarting from checkpoint "
<< param;
} else if (event_str[0] == 't') {
int64_t param = strtoll(event_str.c_str() + 1, &endp, 0);
req.restart().type = RESTART_FROM_TICKS;
req.restart().param_str = event_str.substr(1);
req.restart().param = param;
LOG(debug) << "next replayer restarting from tick count "
<< param;
} else {
req.restart().type = RESTART_FROM_EVENT;
req.restart().param = strtoll(event_str.c_str(), &endp, 0);
LOG(debug) << "next replayer advancing to event " << req.restart().param;
}
if (!endp || *endp != '\0') {
LOG(debug) << "Couldn't parse event string `" << event_str << "'"
<< "; restarting from previous";
req.restart().type = RESTART_FROM_PREVIOUS;
req.restart().param = -1;
}
return true;
}
if (name == strstr(name, "File:")) {
char* operation = payload + 5;
if (operation == strstr(operation, "open:")) {
char* file_name_end = strchr(operation + 5, ',');
parser_assert(file_name_end != NULL);
*file_name_end = 0;
req = GdbRequest(DREQ_FILE_OPEN);
req.file_open().file_name = decode_ascii_encoded_hex_str(operation + 5);
char* flags_end;
int64_t flags = strtol(file_name_end + 1, &flags_end, 16);
parser_assert(*flags_end == ',');
req.file_open().flags = gdb_open_flags_to_system_flags(flags);
char* mode_end;
int64_t mode = strtol(flags_end + 1, &mode_end, 16);
parser_assert(*mode_end == 0);
parser_assert((mode & ~(int64_t)0777) == 0);
req.file_open().mode = mode;
return true;
} else if (operation == strstr(operation, "close:")) {
char* endptr;
int64_t fd = strtol(operation + 6, &endptr, 16);
parser_assert(*endptr == 0);
req = GdbRequest(DREQ_FILE_CLOSE);
req.file_close().fd = fd;
parser_assert(req.file_close().fd == fd);
return true;
} else if (operation == strstr(operation, "pread:")) {
char* fd_end;
int64_t fd = strtol(operation + 6, &fd_end, 16);
parser_assert(*fd_end == ',');
req = GdbRequest(DREQ_FILE_PREAD);
req.file_pread().fd = fd;
parser_assert(req.file_pread().fd == fd);
char* size_end;
int64_t size = strtol(fd_end + 1, &size_end, 16);
parser_assert(*size_end == ',');
parser_assert(size >= 0);
req.file_pread().size = size;
char* offset_end;
int64_t offset = strtol(size_end + 1, &offset_end, 16);
parser_assert(*offset_end == 0);
parser_assert(offset >= 0);
req.file_pread().offset = offset;
return true;
} else if (operation == strstr(operation, "setfs:")) {
char* endptr;
int64_t pid = strtol(operation + 6, &endptr, 16);
parser_assert(*endptr == 0);
req = GdbRequest(DREQ_FILE_SETFS);
req.file_setfs().pid = pid;
parser_assert(req.file_setfs().pid == pid);
return true;
} else {
write_packet("");
return false;
}
}
UNHANDLED_REQ() << "Unhandled gdb vpacket: v" << name;
return false;
}
static string to_string(const vector<uint8_t>& bytes, size_t max_len) {
stringstream ss;
for (size_t i = 0; i < bytes.size(); ++i) {
if (i >= max_len) {
ss << "...";
break;
}
char buf[3];
sprintf(buf, "%02x", bytes[i]);
ss << buf;
}
return ss.str();
}
bool GdbConnection::process_packet() {
parser_assert(
INTERRUPT_CHAR == inbuf[0] ||
('$' == inbuf[0] && (uint8_t*)memchr(inbuf.data(), '#', inbuf.size()) ==
inbuf.data() + packetend));
if (INTERRUPT_CHAR == inbuf[0]) {
LOG(debug) << "gdb requests interrupt";
req = GdbRequest(DREQ_INTERRUPT);
inbuf.erase(inbuf.begin());
return true;
}
char request = inbuf[1];
char* payload = (char*)&inbuf[2];
inbuf[packetend] = '\0';
LOG(debug) << "raw request " << request << payload;
bool ret;
switch (request) {
case 'b':
ret = process_bpacket(payload);
break;
case 'c':
LOG(debug) << "gdb is asking to continue";
req = GdbRequest(DREQ_CONT);
req.cont().run_direction = RUN_FORWARD;
req.cont().actions.push_back(GdbContAction(ACTION_CONTINUE));
ret = true;
break;
case 'D':
LOG(debug) << "gdb is detaching from us";
req = GdbRequest(DREQ_DETACH);
ret = true;
break;
case 'g':
req = GdbRequest(DREQ_GET_REGS);
req.target = query_thread;
LOG(debug) << "gdb requests registers";
ret = true;
break;
case 'G':
/* XXX we can't let gdb spray registers in general,
* because it may cause replay to diverge. But some
* writes may be OK. Let's see how far we can get
* with ignoring these requests. */
write_packet("");
ret = false;
break;
case 'H':
if ('c' == *payload++) {
req = GdbRequest(DREQ_SET_CONTINUE_THREAD);
} else {
req = GdbRequest(DREQ_SET_QUERY_THREAD);
}
req.target = parse_threadid(payload, &payload);
parser_assert('\0' == *payload);
LOG(debug) << "gdb selecting " << req.target;
ret = true;
break;
case 'k':
LOG(info) << "gdb requests kill, exiting";
write_packet("OK");
exit(0);
case 'm':
req = GdbRequest(DREQ_GET_MEM);
req.target = query_thread;
req.mem().addr = strtoul(payload, &payload, 16);
parser_assert(',' == *payload++);
req.mem().len = strtoul(payload, &payload, 16);
parser_assert('\0' == *payload);
LOG(debug) << "gdb requests memory (addr=" << HEX(req.mem().addr)
<< ", len=" << req.mem().len << ")";
ret = true;
break;
case 'M':
/* We can't allow the debugger to write arbitrary data
* to memory, or the replay may diverge. */
// TODO: parse this packet in case some oddball gdb
// decides to send it instead of 'X'
write_packet("");
ret = false;
break;
case 'p':
req = GdbRequest(DREQ_GET_REG);
req.target = query_thread;
req.reg().name = GdbRegister(strtoul(payload, &payload, 16));
parser_assert('\0' == *payload);
LOG(debug) << "gdb requests register value (" << req.reg().name << ")";
ret = true;
break;
case 'P':
req = GdbRequest(DREQ_SET_REG);
req.target = query_thread;
req.reg().name = GdbRegister(strtoul(payload, &payload, 16));
parser_assert('=' == *payload++);
read_reg_value(&payload, &req.reg());
parser_assert('\0' == *payload);
ret = true;
break;
case 'q':
ret = query(payload);
break;
case 'Q':
ret = set_var(payload);
break;
case 'T':
req = GdbRequest(DREQ_GET_IS_THREAD_ALIVE);
req.target = parse_threadid(payload, &payload);
parser_assert('\0' == *payload);
LOG(debug) << "gdb wants to know if " << req.target << " is alive";
ret = true;
break;
case 'v':
ret = process_vpacket(payload);
break;
case 'X': {
req = GdbRequest(DREQ_SET_MEM);
req.target = query_thread;
req.mem().addr = strtoul(payload, &payload, 16);
parser_assert(',' == *payload++);
req.mem().len = strtoul(payload, &payload, 16);
parser_assert(':' == *payload++);
read_binary_data((const uint8_t*)payload, inbuf.data() + packetend,
req.mem().data);
parser_assert(req.mem().len == req.mem().data.size());
LOG(debug) << "gdb setting memory (addr=" << HEX(req.mem().addr)
<< ", len=" << req.mem().len
<< ", data=" << to_string(req.mem().data, 32) << ")";
ret = true;
break;
}
case 'z':
case 'Z': {
int type = strtol(payload, &payload, 16);
parser_assert(',' == *payload++);
if (!(0 <= type && type <= 4)) {
LOG(warn) << "Unknown watch type " << type;
write_packet("");
ret = false;
break;
}
req = GdbRequest(GdbRequestType(
type + (request == 'Z' ? DREQ_SET_SW_BREAK : DREQ_REMOVE_SW_BREAK)));
req.watch().addr = strtoul(payload, &payload, 16);
parser_assert(',' == *payload);
payload++;
req.watch().kind = strtoul(payload, &payload, 16);
if (';' == *payload) {
++payload;
while ('X' == *payload) {
++payload;
int len = strtol(payload, &payload, 16);
parser_assert(',' == *payload);
payload++;
vector<uint8_t> bytes;
for (int i = 0; i < len; ++i) {
parser_assert(payload[0] && payload[1]);
char tmp = payload[2];
payload[2] = '\0';
bytes.push_back(strtol(payload, &payload, 16));
parser_assert('\0' == *payload);
payload[0] = tmp;
}
req.watch().conditions.push_back(std::move(bytes));
}
}
parser_assert('\0' == *payload);
LOG(debug) << "gdb requests " << ('Z' == request ? "set" : "remove")
<< "breakpoint (addr=" << HEX(req.watch().addr)
<< ", len=" << req.watch().kind << ")";
ret = true;
break;
}
case '!':
LOG(debug) << "gdb requests extended mode";
write_packet("OK");
ret = false;
break;
case '?':
LOG(debug) << "gdb requests stop reason";
req = GdbRequest(DREQ_GET_STOP_REASON);
req.target = query_thread;
ret = true;
break;
default:
UNHANDLED_REQ() << "Unhandled gdb request '" << inbuf[1] << "'";
ret = false;
}
/* Erase the newly processed packet from the input buffer. The checksum
* after the '#' will be skipped later as we look for the next packet start.
*/
inbuf.erase(inbuf.begin(), inbuf.begin() + packetend + 1);
/* If we processed the request internally, consume it. */
if (!ret) {
consume_request();
}
return ret;
}
void GdbConnection::notify_no_such_thread(const GdbRequest& req) {
DEBUG_ASSERT(req.target == this->req.target && req.type == this->req.type);
/* '10' is the errno ECHILD. We use it as a magic code to
* notify the user that the thread that was the target of this
* request has died, and either gdb didn't notice that, or rr
* didn't notify gdb. Either way, the user should restart
* their debugging session. */
LOG(error) << "Targeted thread no longer exists; this is the result of "
"either a gdb or\n"
"rr bug. Please restart your debugging session and avoid "
"doing whatever\n"
"triggered this bug.";
write_packet("E10");
consume_request();
}
void GdbConnection::notify_restart() {
DEBUG_ASSERT(DREQ_RESTART == req.type);
// These threads may not exist at the first trace-stop after
// restart. The gdb client should reset this state, but help
// it out just in case.
resume_thread = GdbThreadId::ANY;
query_thread = GdbThreadId::ANY;
req = GdbRequest();
}
GdbRequest GdbConnection::get_request() {
if (DREQ_RESTART == req.type) {
LOG(debug) << "consuming RESTART request";
notify_restart();
// gdb wants to be notified with a stop packet when
// the process "relaunches". In rr's case, the
// traceee may be very far away from process creation,
// but that's OK.
req = GdbRequest(DREQ_GET_STOP_REASON);
req.target = query_thread;
return req;
}
/* Can't ask for the next request until you've satisfied the
* current one, for requests that need an immediate
* response. */
#ifdef DEBUG
DEBUG_ASSERT(!request_needs_immediate_response(&req));
#endif
if (!sniff_packet() && req.is_resume_request()) {
/* There's no new request data available and gdb has
* already asked us to resume. OK, do that (or keep
* doing that) now. */
return req;
}
while (true) {
/* There's either new request data, or we have nothing
* to do. Either way, block until we read a complete
* packet from gdb. */
read_packet();
if (!connection_alive_) {
return req = GdbRequest(DREQ_DETACH);
}
if (process_packet()) {
/* We couldn't process the packet internally,
* so the target has to do something. */
return req;
}
/* The packet we got was "internal", gdb details.
* Nothing for the target to do yet. Keep waiting. */
}
}
void GdbConnection::notify_exit_code(int code) {
char buf[64];
DEBUG_ASSERT(req.is_resume_request() || req.type == DREQ_INTERRUPT);
snprintf(buf, sizeof(buf) - 1, "W%02x", code);
write_packet(buf);
consume_request();
}
void GdbConnection::notify_exit_signal(int sig) {
char buf[64];
DEBUG_ASSERT(req.is_resume_request() || req.type == DREQ_INTERRUPT);
snprintf(buf, sizeof(buf) - 1, "X%02x", sig);
write_packet(buf);
consume_request();
}
/**
* Translate linux-x86 |sig| to gdb's internal numbering. Translation
* made according to gdb/include/gdb/signals.def.
*/
static int to_gdb_signum(int sig) {
switch (sig) {
case 0:
return 0;
case SIGHUP:
return 1;
case SIGINT:
return 2;
case SIGQUIT:
return 3;
case SIGILL:
return 4;
case SIGTRAP:
return 5;
case SIGABRT /*case SIGIOT*/:
return 6;
case SIGBUS:
return 10;
case SIGFPE:
return 8;
case SIGKILL:
return 9;
case SIGUSR1:
return 30;
case SIGSEGV:
return 11;
case SIGUSR2:
return 31;
case SIGPIPE:
return 13;
case SIGALRM:
return 14;
case SIGTERM:
return 15;
/* gdb hasn't heard of SIGSTKFLT, so this is
* arbitrarily made up. SIGDANGER just sounds cool.*/
case SIGSTKFLT:
return 38 /*GDB_SIGNAL_DANGER*/;
/*case SIGCLD*/ case SIGCHLD:
return 20;
case SIGCONT:
return 19;
case SIGSTOP:
return 17;
case SIGTSTP:
return 18;
case SIGTTIN:
return 21;
case SIGTTOU:
return 22;
case SIGURG:
return 16;
case SIGXCPU:
return 24;
case SIGXFSZ:
return 25;
case SIGVTALRM:
return 26;
case SIGPROF:
return 27;
case SIGWINCH:
return 28;
/*case SIGPOLL*/ case SIGIO:
return 23;
case SIGPWR:
return 32;
case SIGSYS:
return 12;
case 32:
return 77;
default:
if (33 <= sig && sig <= 63) {
/* GDB_SIGNAL_REALTIME_33 is numbered 45, hence this offset. */
return sig + 12;
}
if (64 <= sig && sig <= 127) {
/* GDB_SIGNAL_REALTIME_64 is numbered 78, hence this offset. */
return sig + 14;
}
LOG(warn) << "Unknown signal " << sig;
return 143; // GDB_SIGNAL_UNKNOWN
}
}
void GdbConnection::send_stop_reply_packet(GdbThreadId thread, int sig,
const char *reason) {
if (sig < 0) {
write_packet("E01");
return;
}
char buf[PATH_MAX];
if (multiprocess_supported_) {
snprintf(buf, sizeof(buf) - 1, "T%02xthread:p%02x.%02x;%s",
to_gdb_signum(sig), thread.pid, thread.tid, reason);
} else {
snprintf(buf, sizeof(buf) - 1, "T%02xthread:%02x;%s",
to_gdb_signum(sig), thread.tid, reason);
}
write_packet(buf);
}
void GdbConnection::notify_stop(GdbThreadId thread, int sig,
const char *reason) {
DEBUG_ASSERT(req.is_resume_request() || req.type == DREQ_INTERRUPT);
// don't pass this signal to gdb if it is specified not to
if (pass_signals.find(to_gdb_signum(sig)) != pass_signals.end()) {
LOG(debug) << "discarding stop notification for signal " << sig
<< " on thread " << thread << " as specified by QPassSignal";
return;
}
if (tgid != thread.pid) {
LOG(debug) << "ignoring stop of " << thread
<< " because we're debugging tgid " << tgid;
// Re-use the existing continue request to advance to
// the next stop we're willing to tell gdb about.
return;
}
if (!reason) {
reason = "";
}
send_stop_reply_packet(thread, sig, reason);
// This isn't documented in the gdb remote protocol, but if we
// don't do this, gdb will sometimes continue to send requests
// for the previously-stopped thread when it obviously intends
// to be making requests about the stopped thread.
// To make things even better, gdb expects different behavior
// for forward continue/interrupt and reverse continue.
if (req.is_resume_request() && req.cont().run_direction == RUN_BACKWARD) {
LOG(debug) << "Setting query/resume_thread to ANY after reverse continue";
query_thread = resume_thread = GdbThreadId::ANY;
} else {
LOG(debug) << "Setting query/resume_thread to " << thread
<< " after forward continue or interrupt";
query_thread = resume_thread = thread;
}
consume_request();
}
void GdbConnection::notify_restart_failed() {
DEBUG_ASSERT(DREQ_RESTART == req.type);
// TODO: it's not known by this author whether gdb knows how
// to recover from a failed "run" request.
write_packet("E01");
consume_request();
}
void GdbConnection::reply_get_current_thread(GdbThreadId thread) {
DEBUG_ASSERT(DREQ_GET_CURRENT_THREAD == req.type);
char buf[1024];
if (multiprocess_supported_) {
snprintf(buf, sizeof(buf), "QCp%02x.%02x", thread.pid, thread.tid);
} else {
snprintf(buf, sizeof(buf), "QC%02x", thread.tid);
}
write_packet(buf);
consume_request();
}
void GdbConnection::reply_get_auxv(const vector<uint8_t>& auxv) {
DEBUG_ASSERT(DREQ_GET_AUXV == req.type);
if (!auxv.empty()) {
write_binary_packet("l", auxv.data(), auxv.size());
} else {
write_packet("E01");
}
consume_request();
}
void GdbConnection::reply_get_exec_file(const string& exec_file) {
DEBUG_ASSERT(DREQ_GET_EXEC_FILE == req.type);
if (!exec_file.empty()) {
write_binary_packet("l",
reinterpret_cast<const uint8_t*>(exec_file.c_str()),
exec_file.size());
} else {
write_packet("E01");
}
consume_request();
}
void GdbConnection::reply_get_is_thread_alive(bool alive) {
DEBUG_ASSERT(DREQ_GET_IS_THREAD_ALIVE == req.type);
write_packet(alive ? "OK" : "E01");
consume_request();
}
void GdbConnection::reply_get_thread_extra_info(const string& info) {
DEBUG_ASSERT(DREQ_GET_THREAD_EXTRA_INFO == req.type);
LOG(debug) << "thread extra info: '" << info.c_str() << "'";
write_hex_bytes_packet((const uint8_t*)info.c_str(), 1 + info.length());
consume_request();
}
void GdbConnection::reply_select_thread(bool ok) {
DEBUG_ASSERT(DREQ_SET_CONTINUE_THREAD == req.type ||
DREQ_SET_QUERY_THREAD == req.type);
if (ok && DREQ_SET_CONTINUE_THREAD == req.type) {
resume_thread = req.target;
} else if (ok && DREQ_SET_QUERY_THREAD == req.type) {
query_thread = req.target;
}
write_packet(ok ? "OK" : "E01");
consume_request();
}
void GdbConnection::reply_get_mem(const vector<uint8_t>& mem) {
DEBUG_ASSERT(DREQ_GET_MEM == req.type);
DEBUG_ASSERT(mem.size() <= req.mem().len);
if (req.mem().len > 0 && mem.size() == 0) {
write_packet("E01");
} else {
write_hex_bytes_packet(mem.data(), mem.size());
}
consume_request();
}
void GdbConnection::reply_set_mem(bool ok) {
DEBUG_ASSERT(DREQ_SET_MEM == req.type);
write_packet(ok ? "OK" : "E01");
consume_request();
}
void GdbConnection::reply_search_mem(bool found, remote_ptr<void> addr) {
DEBUG_ASSERT(DREQ_SEARCH_MEM == req.type);
if (found) {
char buf[256];
sprintf(buf, "1,%llx", (long long)addr.as_int());
write_packet(buf);
} else {
write_packet("0");
}
consume_request();
}
void GdbConnection::reply_get_offsets(/* TODO */) {
DEBUG_ASSERT(DREQ_GET_OFFSETS == req.type);
/* XXX FIXME TODO */
write_packet("");
consume_request();
}
void GdbConnection::reply_get_reg(const GdbRegisterValue& reg) {
char buf[2 * GdbRegisterValue::MAX_SIZE + 1];
DEBUG_ASSERT(DREQ_GET_REG == req.type);
print_reg_value(reg, buf);
write_packet(buf);
consume_request();
}
void GdbConnection::reply_get_regs(const vector<GdbRegisterValue>& file) {
std::unique_ptr<char[]> buf(
new char[file.size() * 2 * GdbRegisterValue::MAX_SIZE + 1]);
DEBUG_ASSERT(DREQ_GET_REGS == req.type);
size_t offset = 0;
for (auto& reg : file) {
offset += print_reg_value(reg, &buf[offset]);
}
write_packet(buf.get());
consume_request();
}
void GdbConnection::reply_set_reg(bool ok) {
DEBUG_ASSERT(DREQ_SET_REG == req.type);
// TODO: what happens if we're forced to reply to a
// set-register request with |ok = false|, leading us to
// pretend not to understand the packet? If, later, an
// experimental session needs the set-register request will it
// not be sent?
//
// We can't reply with an error packet here because gdb thinks
// that failed set-register requests are catastrophic.
write_packet(ok ? "OK" : "");
consume_request();
}
void GdbConnection::reply_get_stop_reason(GdbThreadId which, int sig) {
DEBUG_ASSERT(DREQ_GET_STOP_REASON == req.type);
send_stop_reply_packet(which, sig, "");
consume_request();
}
void GdbConnection::reply_get_thread_list(const vector<GdbThreadId>& threads) {
DEBUG_ASSERT(DREQ_GET_THREAD_LIST == req.type);
if (threads.empty()) {
write_packet("l");
} else {
stringstream sstr;
sstr << 'm';
for (size_t i = 0; i < threads.size(); ++i) {
const GdbThreadId& t = threads[i];
if (tgid != t.pid) {
continue;
}
if (multiprocess_supported_) {
sstr << 'p' << setw(2) << setfill('0') << hex << t.pid << dec << '.'
<< setw(2) << setfill('0') << hex << t.tid << ',';
} else {
sstr << setw(2) << setfill('0') << hex << t.tid << ',';
}
}
string str = sstr.str();
/* Overwrite the trailing ',' */
str.back() = 0;
write_packet(str.c_str());
}
consume_request();
}
void GdbConnection::reply_watchpoint_request(bool ok) {
DEBUG_ASSERT(DREQ_WATCH_FIRST <= req.type && req.type <= DREQ_WATCH_LAST);
write_packet(ok ? "OK" : "E01");
consume_request();
}
void GdbConnection::reply_detach() {
DEBUG_ASSERT(DREQ_DETACH <= req.type);
write_packet("OK");
consume_request();
}
void GdbConnection::reply_read_siginfo(const vector<uint8_t>& si_bytes) {
DEBUG_ASSERT(DREQ_READ_SIGINFO == req.type);
if (si_bytes.empty()) {
write_packet("E01");
} else {
write_binary_packet("l", si_bytes.data(), si_bytes.size());
}
consume_request();
}
void GdbConnection::reply_write_siginfo(/* TODO*/) {
DEBUG_ASSERT(DREQ_WRITE_SIGINFO == req.type);
write_packet("E01");
consume_request();
}
void GdbConnection::reply_rr_cmd(const std::string& text) {
DEBUG_ASSERT(DREQ_RR_CMD == req.type);
write_packet(text.c_str());
consume_request();
}
void GdbConnection::send_qsymbol(const std::string& name) {
DEBUG_ASSERT(DREQ_QSYMBOL == req.type);
const void* data = static_cast<const void*>(name.c_str());
write_hex_bytes_packet("qSymbol:", static_cast<const uint8_t*>(data),
name.length());
consume_request();
}
void GdbConnection::qsymbols_finished() {
DEBUG_ASSERT(DREQ_QSYMBOL == req.type);
write_packet("OK");
consume_request();
}
void GdbConnection::reply_tls_addr(bool ok, remote_ptr<void> address) {
DEBUG_ASSERT(DREQ_TLS == req.type);
if (ok) {
char buf[256];
sprintf(buf, "%llx", (long long)address.as_int());
write_packet(buf);
} else {
write_packet("E01");
}
consume_request();
}
void GdbConnection::reply_setfs(int err) {
DEBUG_ASSERT(DREQ_FILE_SETFS == req.type);
if (err) {
send_file_error_reply(err);
} else {
write_packet("F0");
}
consume_request();
}
void GdbConnection::reply_open(int fd, int err) {
DEBUG_ASSERT(DREQ_FILE_OPEN == req.type);
if (err) {
send_file_error_reply(err);
} else {
char buf[32];
sprintf(buf, "F%x", fd);
write_packet(buf);
}
consume_request();
}
void GdbConnection::reply_pread(const uint8_t* bytes, ssize_t len, int err) {
DEBUG_ASSERT(DREQ_FILE_PREAD == req.type);
if (err) {
send_file_error_reply(err);
} else {
char buf[32];
sprintf(buf, "F%llx;", (long long)len);
write_binary_packet(buf, bytes, len);
}
consume_request();
}
void GdbConnection::reply_close(int err) {
DEBUG_ASSERT(DREQ_FILE_CLOSE == req.type);
if (err) {
send_file_error_reply(err);
} else {
write_packet("F0");
}
consume_request();
}
void GdbConnection::send_file_error_reply(int system_errno) {
int gdb_err;
switch (system_errno) {
case EPERM:
gdb_err = 1;
break;
case ENOENT:
gdb_err = 2;
break;
case EINTR:
gdb_err = 4;
break;
case EBADF:
gdb_err = 9;
break;
case EACCES:
gdb_err = 13;
break;
case EFAULT:
gdb_err = 14;
break;
case EBUSY:
gdb_err = 16;
break;
case EEXIST:
gdb_err = 17;
break;
case ENODEV:
gdb_err = 19;
break;
case ENOTDIR:
gdb_err = 20;
break;
case EISDIR:
gdb_err = 21;
break;
case EINVAL:
gdb_err = 22;
break;
case ENFILE:
gdb_err = 23;
break;
case EMFILE:
gdb_err = 24;
break;
case EFBIG:
gdb_err = 27;
break;
case ENOSPC:
gdb_err = 28;
break;
case ESPIPE:
gdb_err = 29;
break;
case EROFS:
gdb_err = 30;
break;
case ENAMETOOLONG:
gdb_err = 91;
break;
default:
gdb_err = 9999;
break;
}
char buf[32];
sprintf(buf, "F-01,%x", gdb_err);
write_packet(buf);
}
bool GdbConnection::is_connection_alive() { return connection_alive_; }
bool GdbConnection::is_pass_signal(int sig) { return pass_signals.find(to_gdb_signum(sig)) != pass_signals.end(); }
} // namespace rr