blob: 9ca557e52efbf9abeabe4ce853bb512ad6e14923 [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
#ifndef RR_GDB_CONNECTION_H_
#define RR_GDB_CONNECTION_H_
#include <stddef.h>
#include <sys/types.h>
#include <memory>
#include <ostream>
#include <string>
#include <unordered_set>
#include <vector>
#include "GdbRegister.h"
#include "Registers.h"
#include "ReplaySession.h"
#include "ReplayTimeline.h"
#include "core.h"
namespace rr {
/**
* Descriptor for task. Note: on linux, we can uniquely identify any thread
* by its |tid| (in rr's pid namespace).
*/
struct GdbThreadId {
GdbThreadId(pid_t pid = -1, pid_t tid = -1) : pid(pid), tid(tid) {}
pid_t pid;
pid_t tid;
bool operator==(const GdbThreadId& o) const {
return pid == o.pid && tid == o.tid;
}
static const GdbThreadId ANY;
static const GdbThreadId ALL;
};
inline std::ostream& operator<<(std::ostream& o, const GdbThreadId& t) {
o << t.pid << "." << t.tid;
return o;
}
/**
* Represents a possibly-undefined register |name|. |size| indicates how
* many bytes of |value| are valid, if any.
*/
struct GdbRegisterValue {
enum { MAX_SIZE = Registers::MAX_SIZE };
GdbRegister name;
union {
uint8_t value[MAX_SIZE];
uint8_t value1;
uint16_t value2;
uint32_t value4;
uint64_t value8;
};
size_t size;
bool defined;
};
enum GdbRequestType {
DREQ_NONE = 0,
/* None of these requests have parameters. */
DREQ_GET_CURRENT_THREAD,
DREQ_GET_OFFSETS,
DREQ_GET_REGS,
DREQ_GET_STOP_REASON,
DREQ_GET_THREAD_LIST,
DREQ_INTERRUPT,
DREQ_DETACH,
/* These use params.target. */
DREQ_GET_AUXV,
DREQ_GET_EXEC_FILE,
DREQ_GET_IS_THREAD_ALIVE,
DREQ_GET_THREAD_EXTRA_INFO,
DREQ_SET_CONTINUE_THREAD,
DREQ_SET_QUERY_THREAD,
// TLS lookup, uses params.target and params.tls.
DREQ_TLS,
// gdb wants to write back siginfo_t to a tracee. More
// importantly, this packet arrives before an experiment
// session for a |call foo()| is about to be torn down.
//
// TODO: actual interface NYI.
DREQ_WRITE_SIGINFO,
/* These use params.mem. */
DREQ_GET_MEM,
DREQ_SET_MEM,
// gdb wants to read the current siginfo_t for a stopped
// tracee. More importantly, this packet arrives at the very
// beginning of a |call foo()| experiment.
//
// Uses .mem for offset/len.
DREQ_READ_SIGINFO,
DREQ_SEARCH_MEM,
DREQ_MEM_FIRST = DREQ_GET_MEM,
DREQ_MEM_LAST = DREQ_SEARCH_MEM,
DREQ_REMOVE_SW_BREAK,
DREQ_REMOVE_HW_BREAK,
DREQ_REMOVE_WR_WATCH,
DREQ_REMOVE_RD_WATCH,
DREQ_REMOVE_RDWR_WATCH,
DREQ_SET_SW_BREAK,
DREQ_SET_HW_BREAK,
DREQ_SET_WR_WATCH,
DREQ_SET_RD_WATCH,
DREQ_SET_RDWR_WATCH,
DREQ_WATCH_FIRST = DREQ_REMOVE_SW_BREAK,
DREQ_WATCH_LAST = DREQ_SET_RDWR_WATCH,
/* Use params.reg. */
DREQ_GET_REG,
DREQ_SET_REG,
DREQ_REG_FIRST = DREQ_GET_REG,
DREQ_REG_LAST = DREQ_SET_REG,
/* Use params.cont. */
DREQ_CONT,
/* gdb host detaching from stub. No parameters. */
/* Uses params.restart. */
DREQ_RESTART,
/* Uses params.text. */
DREQ_RR_CMD,
// qSymbol packet, uses params.sym.
DREQ_QSYMBOL,
// vFile:setfs packet, uses params.file_setfs.
DREQ_FILE_SETFS,
// vFile:open packet, uses params.file_open.
DREQ_FILE_OPEN,
// vFile:pread packet, uses params.file_pread.
DREQ_FILE_PREAD,
// vFile:close packet, uses params.file_close.
DREQ_FILE_CLOSE,
};
enum GdbRestartType {
RESTART_FROM_PREVIOUS,
RESTART_FROM_EVENT,
RESTART_FROM_CHECKPOINT,
RESTART_FROM_TICKS
};
enum GdbActionType { ACTION_CONTINUE, ACTION_STEP };
struct GdbContAction {
GdbContAction(GdbActionType type = ACTION_CONTINUE,
const GdbThreadId& target = GdbThreadId::ANY,
int signal_to_deliver = 0)
: type(type), target(target), signal_to_deliver(signal_to_deliver) {}
GdbActionType type;
GdbThreadId target;
int signal_to_deliver;
};
/**
* These requests are made by the debugger host and honored in proxy
* by rr, the target.
*/
struct GdbRequest {
GdbRequest(GdbRequestType type = DREQ_NONE)
: type(type), suppress_debugger_stop(false) {}
GdbRequest(const GdbRequest& other)
: type(other.type),
target(other.target),
suppress_debugger_stop(other.suppress_debugger_stop),
mem_(other.mem_),
watch_(other.watch_),
reg_(other.reg_),
restart_(other.restart_),
cont_(other.cont_),
text_(other.text_),
tls_(other.tls_),
sym_(other.sym_),
file_setfs_(other.file_setfs_),
file_open_(other.file_open_),
file_pread_(other.file_pread_),
file_close_(other.file_close_) {}
GdbRequest& operator=(const GdbRequest& other) {
this->~GdbRequest();
new (this) GdbRequest(other);
return *this;
}
const GdbRequestType type;
GdbThreadId target;
bool suppress_debugger_stop;
struct Mem {
uintptr_t addr;
size_t len;
// For SET_MEM requests, the |len| raw bytes that are to be written.
// For SEARCH_MEM requests, the bytes to search for.
std::vector<uint8_t> data;
} mem_;
struct Watch {
uintptr_t addr;
int kind;
std::vector<std::vector<uint8_t>> conditions;
} watch_;
GdbRegisterValue reg_;
struct Restart {
int64_t param;
std::string param_str;
GdbRestartType type;
} restart_;
struct Cont {
RunDirection run_direction;
std::vector<GdbContAction> actions;
} cont_;
std::string text_;
struct Tls {
size_t offset;
remote_ptr<void> load_module;
} tls_;
struct Symbol {
bool has_address;
remote_ptr<void> address;
std::string name;
} sym_;
struct FileSetfs {
pid_t pid;
} file_setfs_;
struct FileOpen {
std::string file_name;
// In system format, not gdb's format
int flags;
int mode;
} file_open_;
struct FilePread {
int fd;
size_t size;
uint64_t offset;
} file_pread_;
struct FileClose {
int fd;
} file_close_;
Mem& mem() {
DEBUG_ASSERT(type >= DREQ_MEM_FIRST && type <= DREQ_MEM_LAST);
return mem_;
}
const Mem& mem() const {
DEBUG_ASSERT(type >= DREQ_MEM_FIRST && type <= DREQ_MEM_LAST);
return mem_;
}
Watch& watch() {
DEBUG_ASSERT(type >= DREQ_WATCH_FIRST && type <= DREQ_WATCH_LAST);
return watch_;
}
const Watch& watch() const {
DEBUG_ASSERT(type >= DREQ_WATCH_FIRST && type <= DREQ_WATCH_LAST);
return watch_;
}
GdbRegisterValue& reg() {
DEBUG_ASSERT(type >= DREQ_REG_FIRST && type <= DREQ_REG_LAST);
return reg_;
}
const GdbRegisterValue& reg() const {
DEBUG_ASSERT(type >= DREQ_REG_FIRST && type <= DREQ_REG_LAST);
return reg_;
}
Restart& restart() {
DEBUG_ASSERT(type == DREQ_RESTART);
return restart_;
}
const Restart& restart() const {
DEBUG_ASSERT(type == DREQ_RESTART);
return restart_;
}
Cont& cont() {
DEBUG_ASSERT(type == DREQ_CONT);
return cont_;
}
const Cont& cont() const {
DEBUG_ASSERT(type == DREQ_CONT);
return cont_;
}
const std::string& text() const {
DEBUG_ASSERT(type == DREQ_RR_CMD);
return text_;
}
Tls& tls() {
DEBUG_ASSERT(type == DREQ_TLS);
return tls_;
}
const Tls& tls() const {
DEBUG_ASSERT(type == DREQ_TLS);
return tls_;
}
Symbol& sym() {
DEBUG_ASSERT(type == DREQ_QSYMBOL);
return sym_;
}
const Symbol& sym() const {
DEBUG_ASSERT(type == DREQ_QSYMBOL);
return sym_;
}
FileSetfs& file_setfs() {
DEBUG_ASSERT(type == DREQ_FILE_SETFS);
return file_setfs_;
}
const FileSetfs& file_setfs() const {
DEBUG_ASSERT(type == DREQ_FILE_SETFS);
return file_setfs_;
}
FileOpen& file_open() {
DEBUG_ASSERT(type == DREQ_FILE_OPEN);
return file_open_;
}
const FileOpen& file_open() const {
DEBUG_ASSERT(type == DREQ_FILE_OPEN);
return file_open_;
}
FilePread& file_pread() {
DEBUG_ASSERT(type == DREQ_FILE_PREAD);
return file_pread_;
}
const FilePread& file_pread() const {
DEBUG_ASSERT(type == DREQ_FILE_PREAD);
return file_pread_;
}
FileClose& file_close() {
DEBUG_ASSERT(type == DREQ_FILE_CLOSE);
return file_close_;
}
const FileClose& file_close() const {
DEBUG_ASSERT(type == DREQ_FILE_CLOSE);
return file_close_;
}
/**
* Return nonzero if this requires that program execution be resumed
* in some way.
*/
bool is_resume_request() const { return type == DREQ_CONT; }
};
/**
* This struct wraps up the state of the gdb protocol, so that we can
* offer a (mostly) stateless interface to clients.
*/
class GdbConnection {
public:
struct Features {
Features() : reverse_execution(true) {}
bool reverse_execution;
};
/**
* Call this when the target of |req| is needed to fulfill the
* request, but the target is dead. This situation is a symptom of a
* gdb or rr bug.
*/
void notify_no_such_thread(const GdbRequest& req);
/**
* Finish a DREQ_RESTART request. Should be invoked after replay
* restarts and prior GdbConnection has been restored.
*/
void notify_restart();
/**
* Return the current request made by the debugger host, that needs to
* be satisfied. This function will block until either there's a
* debugger host request that needs a response, or until a request is
* made to resume execution of the target. In the latter case,
* calling this function multiple times will return an appropriate
* resume request each time (see above).
*
* The target should peek at the debugger request in between execution
* steps. A new request may need to be serviced.
*/
GdbRequest get_request();
/**
* Notify the host that this process has exited with |code|.
*/
void notify_exit_code(int code);
/**
* Notify the host that this process has exited from |sig|.
*/
void notify_exit_signal(int sig);
/**
* Notify the host that a resume request has "finished", i.e., the
* target has stopped executing for some reason. |sig| is the signal
* that stopped execution, or 0 if execution stopped otherwise.
*/
void notify_stop(GdbThreadId which, int sig, const char *reason=nullptr);
/** Notify the debugger that a restart request failed. */
void notify_restart_failed();
/**
* Tell the host that |thread| is the current thread.
*/
void reply_get_current_thread(GdbThreadId thread);
/**
* Reply with the target thread's |auxv| pairs. |auxv.empty()|
* if there was an error reading the auxiliary vector.
*/
void reply_get_auxv(const std::vector<uint8_t>& auxv);
/**
* Reply with the target thread's executable file name
*/
void reply_get_exec_file(const std::string& exec_file);
/**
* |alive| is true if the requested thread is alive, false if dead.
*/
void reply_get_is_thread_alive(bool alive);
/**
* |info| is a string containing data about the request target that
* might be relevant to the debugger user.
*/
void reply_get_thread_extra_info(const std::string& info);
/**
* |ok| is true if req->target can be selected, false otherwise.
*/
void reply_select_thread(bool ok);
/**
* The first |mem.size()| bytes of the request were read into |mem|.
* |mem.size()| must be less than or equal to the length of the request.
*/
void reply_get_mem(const std::vector<uint8_t>& mem);
/**
* |ok| is true if a SET_MEM request succeeded, false otherwise. This
* function *must* be called whenever a SET_MEM request is made,
* regardless of success/failure or special interpretation.
*/
void reply_set_mem(bool ok);
/**
* Reply to the DREQ_SEARCH_MEM request.
* |found| is true if we found the searched-for bytes starting at address
* |addr|.
*/
void reply_search_mem(bool found, remote_ptr<void> addr);
/**
* Reply to the DREQ_GET_OFFSETS request.
*/
void reply_get_offsets(/* TODO */);
/**
* Send |value| back to the debugger host. |value| may be undefined.
*/
void reply_get_reg(const GdbRegisterValue& value);
/**
* Send |file| back to the debugger host. |file| may contain
* undefined register values.
*/
void reply_get_regs(const std::vector<GdbRegisterValue>& file);
/**
* Pass |ok = true| iff the requested register was successfully set.
*/
void reply_set_reg(bool ok);
/**
* Reply to the DREQ_GET_STOP_REASON request.
*/
void reply_get_stop_reason(GdbThreadId which, int sig);
/**
* |threads| contains the list of live threads, of which there are
* |len|.
*/
void reply_get_thread_list(const std::vector<GdbThreadId>& threads);
/**
* |ok| is true if the request was successfully applied, false if
* not.
*/
void reply_watchpoint_request(bool ok);
/**
* DREQ_DETACH was processed.
*
* There's no functional reason to reply to the detach request.
* However, some versions of gdb expect a response and time out
* awaiting it, wasting developer time.
*/
void reply_detach();
/**
* Pass the siginfo_t and its size (as requested by the debugger) in
* |si_bytes| and |num_bytes| if successfully read. Otherwise pass
* |si_bytes = nullptr|.
*/
void reply_read_siginfo(const std::vector<uint8_t>& si_bytes);
/**
* Not yet implemented, but call this after a WRITE_SIGINFO request
* anyway.
*/
void reply_write_siginfo(/* TODO*/);
/**
* Send a manual text response to a rr cmd (maintenance) packet.
*/
void reply_rr_cmd(const std::string& text);
/**
* Send a qSymbol response to gdb, requesting the address of the
* symbol |name|.
*/
void send_qsymbol(const std::string& name);
/**
* The "all done" response to a qSymbol packet from gdb.
*/
void qsymbols_finished();
/**
* Respond to a qGetTLSAddr packet. If |ok| is true, then respond
* with |address|. If |ok| is false, respond with an error.
*/
void reply_tls_addr(bool ok, remote_ptr<void> address);
/**
* Respond to a vFile:setfs
*/
void reply_setfs(int err);
/**
* Respond to a vFile:open
*/
void reply_open(int fd, int err);
/**
* Respond to a vFile:pread
*/
void reply_pread(const uint8_t* bytes, ssize_t len, int err);
/**
* Respond to a vFile:close
*/
void reply_close(int err);
/**
* Create a checkpoint of the given Session with the given id. Delete the
* existing checkpoint with that id if there is one.
*/
void created_checkpoint(ReplaySession::shr_ptr& checkpoint,
int checkpoint_id);
/**
* Delete the checkpoint with the given id. Silently fail if the checkpoint
* does not exist.
*/
void delete_checkpoint(int checkpoint_id);
/**
* Get the checkpoint with the given id. Return null if not found.
*/
ReplaySession::shr_ptr get_checkpoint(int checkpoint_id);
/**
* Return true if there's a new packet to be read/process (whether
* incomplete or not), and false if there isn't one.
*/
bool sniff_packet();
const Features& features() { return features_; }
enum {
CPU_X86_64 = 0x1,
CPU_AVX = 0x2,
CPU_AARCH64 = 0x4,
CPU_PKU = 0x8
};
void set_cpu_features(uint32_t features) { cpu_features_ = features; }
uint32_t cpu_features() const { return cpu_features_; }
GdbConnection(pid_t tgid, const Features& features);
/**
* Wait for a debugger client to connect to |dbg|'s socket. Blocks
* indefinitely.
*/
void await_debugger(ScopedFd& listen_fd);
/**
* Returns false if the connection has been closed
*/
bool is_connection_alive();
bool hwbreak_supported() { return hwbreak_supported_; }
bool swbreak_supported() { return swbreak_supported_; }
bool is_pass_signal(int sig);
private:
/**
* read() incoming data exactly one time, successfully. May block.
*/
void read_data_once();
/**
* Send all pending output to gdb. May block.
*/
void write_flush();
void write_data_raw(const uint8_t* data, ssize_t len);
void write_hex(unsigned long hex);
void write_packet_bytes(const uint8_t* data, size_t num_bytes);
void write_packet(const char* data);
void write_binary_packet(const char* pfx, const uint8_t* data,
ssize_t num_bytes);
void write_hex_bytes_packet(const char* prefix, const uint8_t* bytes,
size_t len);
void write_hex_bytes_packet(const uint8_t* bytes, size_t len);
void write_xfer_response(const void* data, size_t size, uint64_t offset,
uint64_t len);
/**
* Consume bytes in the input buffer until start-of-packet ('$') or
* the interrupt character is seen. Does not block. Return true if
* seen, false if not.
*/
bool skip_to_packet_start();
/**
* Block until the sequence of bytes
*
* "[^$]*\$[^#]*#.*"
*
* has been read from the client fd. This is one (or more) gdb
* packet(s).
*/
void read_packet();
/**
* Return true if we need to do something in a debugger request,
* false if we already handled the packet internally.
*/
bool xfer(const char* name, char* args);
/**
* Return true if we need to do something in a debugger request,
* false if we already handled the packet internally.
*/
bool query(char* payload);
/**
* Return true if we need to do something in a debugger request,
* false if we already handled the packet internally.
*/
bool set_var(char* payload);
/**
* Return true if we need to do something in a debugger request,
* false if we already handled the packet internally.
*/
bool process_vpacket(char* payload);
/**
* Return true if we need to do something in a debugger request,
* false if we already handled the packet internally.
*/
bool process_bpacket(char* payload);
/**
* Return true if we need to do something in a debugger request,
* false if we already handled the packet internally.
*/
bool process_packet();
void consume_request();
void send_stop_reply_packet(GdbThreadId thread, int sig,
const char *reason);
void send_file_error_reply(int system_errno);
// Current request to be processed.
GdbRequest req;
// Thread to be resumed.
GdbThreadId resume_thread;
// Thread for get/set requests.
GdbThreadId query_thread;
// gdb and rr don't work well together in multi-process and
// multi-exe-image debugging scenarios, so we pretend only
// this thread group exists when interfacing with gdb
pid_t tgid;
uint32_t cpu_features_;
// true when "no-ack mode" enabled, in which we don't have
// to send ack packets back to gdb. This is a huge perf win.
bool no_ack;
// contains signals (gdb not native) which should be passed directly to the
// debuggee without gdb being informed, speeding up
// reverse execution
std::unordered_set<int> pass_signals;
ScopedFd sock_fd;
std::vector<uint8_t> inbuf; /* buffered input from gdb */
size_t packetend; /* index of '#' character */
std::vector<uint8_t> outbuf; /* buffered output for gdb */
Features features_;
bool connection_alive_;
bool multiprocess_supported_; // client supports multiprocess extension
bool hwbreak_supported_; // client supports hwbreak extension
bool swbreak_supported_; // client supports swbreak extension
};
} // namespace rr
#endif /* RR_GDB_CONNECTION_H_ */