Skip to content

Commit

Permalink
Support QSaveRegisterState and QRestoreRegisterState packets
Browse files Browse the repository at this point in the history
LLDB uses these to save and restore register states around debuggee function calls.
Use these as cues to start/stop diversions.

We're making some assumptions here about how these are used. We start a diversion
at the first `QSaveRegisterState` and end the diversion when that saved state is
restored. This restores not only the registers on the target thread but all
tracee memory and registers on all threads. During a diversion, register states
can be saved and restored in any order, but restoring a register state after the
diversion has ended won't work.
  • Loading branch information
rocallahan committed Mar 24, 2024
1 parent 24495b7 commit 662a7f0
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 18 deletions.
58 changes: 58 additions & 0 deletions src/GdbServer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ using namespace std;

namespace rr {

/**
* The "outermost" saved register state is associated with the diversion.
* "Restoring" it exits the diversion and restores all state of the tracee.
*/
const int DIVERSION_SAVED_REGISTER_STATE = 1;

GdbServer::ConnectionFlags::ConnectionFlags()
: dbg_port(-1),
dbg_host(localhost_addr),
Expand Down Expand Up @@ -755,6 +761,36 @@ void GdbServer::dispatch_debugger_request(Session& session,
LOG(warn) << "WRITE_SIGINFO request outside of diversion session";
dbg->reply_write_siginfo();
return;
case DREQ_SAVE_REGISTER_STATE: {
int state_index = DIVERSION_SAVED_REGISTER_STATE + 1;
while (saved_register_states.find(state_index) != saved_register_states.end()) {
++state_index;
}
SavedRegisters& regs = saved_register_states[state_index];
regs.regs = target->regs();
regs.extra_regs = target->extra_regs();
dbg->reply_save_register_state(true, state_index);
return;
}
case DREQ_RESTORE_REGISTER_STATE: {
if (!session.is_diversion()) {
LOG(error) << "RESTORE_REGISTER_STATE request outside of diversion session";
dbg->reply_restore_register_state(false);
return;
}
int state_index = req.restore_register_state().state_index;
auto it = saved_register_states.find(state_index);
if (it == saved_register_states.end()) {
LOG(error) << "Unknown register state";
dbg->reply_restore_register_state(false);
return;
}
target->set_regs(it->second.regs);
target->set_extra_regs(it->second.extra_regs);
saved_register_states.erase(it);
dbg->reply_restore_register_state(true);
return;
}
case DREQ_RR_CMD:
dbg->reply_rr_cmd(
GdbCommandHandler::process_command(*this, target, req.rr_cmd()));
Expand Down Expand Up @@ -887,6 +923,18 @@ bool GdbServer::diverter_process_debugger_requests(
continue;
}

case DREQ_RESTORE_REGISTER_STATE: {
int state_index = req->restore_register_state().state_index;
if (state_index == DIVERSION_SAVED_REGISTER_STATE) {
diversion_refcount = 0;
dbg->reply_restore_register_state(true);
// This request does not need to be retried outside the diversion
*req = GdbRequest(DREQ_NONE);
return false;
}
break;
}

case DREQ_SET_QUERY_THREAD: {
if (req->target.tid) {
Task* next = diversion_session.find_task(req->target.tid);
Expand Down Expand Up @@ -1214,6 +1262,16 @@ GdbRequest GdbServer::process_debugger_requests(ReportState state) {
// the diversion session
}

if (req.type == DREQ_SAVE_REGISTER_STATE && timeline_) {
dbg->reply_save_register_state(true, DIVERSION_SAVED_REGISTER_STATE);
req = divert(timeline_->current_session());
if (req.type == DREQ_NONE) {
continue;
}
// Carry on to process the request that was rejected by
// the diversion session
}

if (req.is_resume_request()) {
Task* t = current_session().find_task(last_continue_task.tuid);
if (t) {
Expand Down
6 changes: 6 additions & 0 deletions src/GdbServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,12 @@ class GdbServer {
};
// Maps from tgid to the DebuggerMem.
std::unordered_map<ThreadGroupUid, DebuggerMem> debugger_mem;

struct SavedRegisters {
Registers regs;
ExtraRegisters extra_regs;
};
std::unordered_map<int, SavedRegisters> saved_register_states;
};

} // namespace rr
Expand Down
67 changes: 51 additions & 16 deletions src/GdbServerConnection.cc
Original file line number Diff line number Diff line change
Expand Up @@ -910,15 +910,27 @@ bool GdbServerConnection::query(char* payload) {
return false;
}

// LLDB QThreadSuffixSupported extension
static void parse_thread_suffix_threadid(char* payload, GdbThreadId* out) {
char* semicolon = strchr(payload, ';');
if (!semicolon) {
return;
}
parser_assert(!strncmp(semicolon + 1, "thread:", 7));
char* endptr;
*out = parse_threadid(semicolon + 8, &endptr);
*semicolon = 0;
}

bool GdbServerConnection::set_var(char* payload) {
const char* name;
char* args;
GdbThreadId target = query_thread;
parse_thread_suffix_threadid(payload, &target);

args = strchr(payload, ':');
char* args = strchr(payload, ':');
if (args) {
*args++ = '\0';
}
name = payload;
const char* name = payload;

if (!strcmp(name, "StartNoAckMode")) {
write_packet("OK");
Expand Down Expand Up @@ -962,6 +974,19 @@ bool GdbServerConnection::set_var(char* payload) {
write_packet("");
return false;
}
if (!strcmp(name, "SaveRegisterState")) {
req = GdbRequest(DREQ_SAVE_REGISTER_STATE);
req.target = target;
return true;
}
if (!strcmp(name, "RestoreRegisterState")) {
req = GdbRequest(DREQ_RESTORE_REGISTER_STATE);
req.target = target;
char* end;
req.restore_register_state().state_index = strtol(args, &end, 16);
parser_assert(!*end || *end == ';');
return true;
}

UNHANDLED_REQ() << "Unhandled debugger set: Q" << name;
return false;
Expand Down Expand Up @@ -1294,18 +1319,6 @@ static string to_string(const vector<uint8_t>& bytes, size_t max_len) {
return ss.str();
}

// LLDB QThreadSuffixSupported extension
static void parse_thread_suffix_threadid(char* payload, GdbThreadId* out) {
char* semicolon = strchr(payload, ';');
if (!semicolon) {
return;
}
parser_assert(!strncmp(semicolon + 1, "thread:", 7));
char* endptr;
*out = parse_threadid(semicolon + 8, &endptr);
*semicolon = 0;
}

bool GdbServerConnection::process_packet() {
parser_assert(
INTERRUPT_CHAR == inbuf[0] ||
Expand Down Expand Up @@ -2293,6 +2306,28 @@ void GdbServerConnection::send_file_error_reply(int system_errno) {
write_packet(buf);
}

void GdbServerConnection::reply_save_register_state(bool ok, int state_index) {
DEBUG_ASSERT(DREQ_SAVE_REGISTER_STATE == req.type);

if (ok) {
char buf[256];
sprintf(buf, "%llx", (long long)state_index);
write_packet(buf);
} else {
write_packet("E01");
}

consume_request();
}

void GdbServerConnection::reply_restore_register_state(bool ok) {
DEBUG_ASSERT(DREQ_RESTORE_REGISTER_STATE == req.type);

write_packet(ok ? "OK" : "E01");

consume_request();
}

bool GdbServerConnection::is_connection_alive() { return connection_alive_; }

bool GdbServerConnection::is_pass_signal(int sig) { return pass_signals.find(to_gdb_signum(sig)) != pass_signals.end(); }
Expand Down
31 changes: 29 additions & 2 deletions src/GdbServerConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ enum GdbRequestType {
DREQ_MEM_ALLOC,
// Uses params.mem_free
DREQ_MEM_FREE,

DREQ_SAVE_REGISTER_STATE,
// Uses params.restore_register_state
DREQ_RESTORE_REGISTER_STATE,
};

enum GdbRestartType {
Expand Down Expand Up @@ -244,6 +248,8 @@ struct GdbRequest {
mem_alloc_ = other.mem_alloc_;
} else if (type == DREQ_MEM_FREE) {
mem_free_ = other.mem_free_;
} else if (type == DREQ_RESTORE_REGISTER_STATE) {
restore_register_state_ = other.restore_register_state_;
}
}
GdbRequest& operator=(const GdbRequest& other) {
Expand Down Expand Up @@ -310,12 +316,15 @@ struct GdbRequest {
int fd = -1;
} file_close_;
struct MemAlloc {
size_t size;
int prot;
size_t size = 0;
int prot = 0;
} mem_alloc_;
struct MemFree {
remote_ptr<void> address;
} mem_free_;
struct RestoreRegisterState {
int state_index = 0;
} restore_register_state_;

Mem& mem() {
DEBUG_ASSERT(type >= DREQ_MEM_FIRST && type <= DREQ_MEM_LAST);
Expand Down Expand Up @@ -429,6 +438,14 @@ struct GdbRequest {
DEBUG_ASSERT(type == DREQ_MEM_FREE);
return mem_free_;
}
RestoreRegisterState& restore_register_state() {
DEBUG_ASSERT(type == DREQ_RESTORE_REGISTER_STATE);
return restore_register_state_;
}
const RestoreRegisterState& restore_register_state() const {
DEBUG_ASSERT(type == DREQ_RESTORE_REGISTER_STATE);
return restore_register_state_;
}

/**
* Return nonzero if this requires that program execution be resumed
Expand Down Expand Up @@ -691,6 +708,16 @@ class GdbServerConnection {
*/
void reply_close(int err);

/**
* Respond to a QSaveRegisterState.
* -1 for failure.
*/
void reply_save_register_state(bool ok, int state_index);
/**
* Respond to a QRestoreRegisterState.
*/
void reply_restore_register_state(bool ok);

/**
* Create a checkpoint of the given Session with the given id. Delete the
* existing checkpoint with that id if there is one.
Expand Down

0 comments on commit 662a7f0

Please sign in to comment.