From 25063eb69496d344fe1afc73434ba3449e8950aa Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Sat, 11 May 2024 12:41:43 +1200 Subject: [PATCH] Support IPv6 This includes support for specifying IPv6 addresses (and actual hostnames) in --dbghost. It also supports falling back to IPv6 when IPv4 is disabled. We produce the correct command lines for gdb and lldb depending on which protocol is in use. --- src/GdbServer.cc | 19 +++----- src/ReplayCommand.cc | 1 - src/launch_debugger.cc | 66 ++++++++++++++++---------- src/launch_debugger.h | 12 ++++- src/util.cc | 104 +++++++++++++++++++++++++++++++++-------- src/util.h | 20 ++++++-- 6 files changed, 161 insertions(+), 61 deletions(-) diff --git a/src/GdbServer.cc b/src/GdbServer.cc index 45936a18efa..f36ac7c9f54 100644 --- a/src/GdbServer.cc +++ b/src/GdbServer.cc @@ -46,7 +46,6 @@ const int DIVERSION_SAVED_REGISTER_STATE = 1; GdbServer::ConnectionFlags::ConnectionFlags() : dbg_port(-1), - dbg_host(localhost_addr), keep_listening(false), serve_files(false), debugger_params_write_pipe(nullptr) {} @@ -1778,12 +1777,6 @@ void GdbServer::restart_session(const GdbRequest& req) { activate_debugger(); } -struct DebuggerParams { - char exe_image[PATH_MAX]; - char host[16]; // INET_ADDRSTRLEN, omitted for header churn - short port; -}; - void GdbServer::serve_replay(std::shared_ptr session, const Target& target, volatile bool* stop_replaying_to_target, @@ -1807,20 +1800,22 @@ void GdbServer::serve_replay(std::shared_ptr session, // place). So fail with a clearer error message. auto probe = flags.dbg_port > 0 ? DONT_PROBE : PROBE_PORT; Task* t = timeline.current_session().current_task(); - ScopedFd listen_fd = open_socket(flags.dbg_host.c_str(), &port, probe); + OpenedSocket listen_socket = open_socket(flags.dbg_host, port, probe); if (flags.debugger_params_write_pipe) { DebuggerParams params; memset(¶ms, 0, sizeof(params)); strncpy(params.exe_image, t->vm()->exe_image().c_str(), sizeof(params.exe_image) - 1); - strncpy(params.host, flags.dbg_host.c_str(), sizeof(params.host) - 1); - params.port = port; + params.socket_domain = listen_socket.domain; + strncpy(params.host, listen_socket.host.c_str(), sizeof(params.host) - 1); + params.port = listen_socket.port; ssize_t nwritten = write(*flags.debugger_params_write_pipe, ¶ms, sizeof(params)); DEBUG_ASSERT(nwritten == sizeof(params)); } else { - vector cmd = debugger_launch_command(t, flags.dbg_host, port, + vector cmd = debugger_launch_command(t, listen_socket.domain, + listen_socket.host, listen_socket.port, flags.serve_files, flags.debugger_name); fprintf(stderr, "Launch debugger with\n %s\n", to_shell_string(cmd).c_str()); } @@ -1837,7 +1832,7 @@ void GdbServer::serve_replay(std::shared_ptr session, do { LOG(debug) << "initializing debugger connection"; - auto connection = GdbServerConnection::await_connection(t, listen_fd); + auto connection = GdbServerConnection::await_connection(t, listen_socket.fd); GdbServer server(connection, timeline.current_session().current_task(), &timeline, target); diff --git a/src/ReplayCommand.cc b/src/ReplayCommand.cc index 7f05f1adca7..d3acf00dfdf 100644 --- a/src/ReplayCommand.cc +++ b/src/ReplayCommand.cc @@ -151,7 +151,6 @@ struct ReplayFlags { process_created_how(CREATED_NONE), dont_launch_debugger(false), dbg_port(-1), - dbg_host(localhost_addr), keep_listening(false), gdb_binary_file_path("gdb"), redirect(true), diff --git a/src/launch_debugger.cc b/src/launch_debugger.cc index 80345a99925..bf05d7979e5 100644 --- a/src/launch_debugger.cc +++ b/src/launch_debugger.cc @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -151,12 +152,6 @@ static const string& lldb_rr_macros() { return s; } -struct DebuggerParams { - char exe_image[PATH_MAX]; - char host[16]; // INET_ADDRSTRLEN, omitted for header churn - short port; -}; - static void push_default_gdb_options(vector& vec, bool serve_files) { // The gdb protocol uses the "vRun" packet to reload // remote targets. The packet is specified to be like @@ -187,27 +182,48 @@ static void push_default_gdb_options(vector& vec, bool serve_files) { } } -static void push_gdb_target_remote_cmd(vector& vec, const string& host, +static void push_gdb_target_remote_cmd(vector& vec, int socket_domain, + const string& host, unsigned short port) { vec.push_back("-ex"); stringstream ss; - // If we omit the address, then gdb can try to resolve "localhost" which - // in some broken environments may not actually resolve to the local host - ss << "target extended-remote " << host << ":" << port; + switch (socket_domain) { + case AF_INET: + // If we omit the address, then gdb can try to resolve "localhost" which + // in some broken environments may not actually resolve to the local host + ss << "target extended-remote " << host << ":" << port; + break; + case AF_INET6: + ss << "target extended-remote tcp6:[" << host << "]:" << port; + break; + default: + FATAL() << "Unknown socket domain " << socket_domain; + break; + } vec.push_back(ss.str()); } -static void push_lldb_target_remote_cmd(vector& vec, const string& host, +static void push_lldb_target_remote_cmd(vector& vec, int socket_domain, + const string& host, unsigned short port) { vec.push_back("-o"); stringstream ss; - ss << "gdb-remote " << host << ":" << port; + switch (socket_domain) { + case AF_INET: + case AF_INET6: + ss << "gdb-remote [" << host << "]:" << port; + break; + default: + FATAL() << "Unknown socket domain " << socket_domain; + break; + } vec.push_back(ss.str()); } string saved_debugger_launch_command; -vector debugger_launch_command(Task* t, const string& host, +vector debugger_launch_command(Task* t, int socket_domain, + const string& host, unsigned short port, bool serve_files, const string& debugger_name) { @@ -216,11 +232,11 @@ vector debugger_launch_command(Task* t, const string& host, switch (identify_debugger(debugger_name)) { case DebuggerType::GDB: push_default_gdb_options(cmd, serve_files); - push_gdb_target_remote_cmd(cmd, host, port); + push_gdb_target_remote_cmd(cmd, socket_domain, host, port); break; case DebuggerType::LLDB: cmd.push_back("--source-quietly"); - push_lldb_target_remote_cmd(cmd, host, port); + push_lldb_target_remote_cmd(cmd, socket_domain, host, port); break; default: FATAL() << "Unknown debugger type"; @@ -283,7 +299,8 @@ void launch_debugger(ScopedFd& params_pipe_fd, } DEBUG_ASSERT(nread == sizeof(params)); - string host(params.host); + const string& host(params.host); + int socket_domain(params.socket_domain); uint16_t port(params.port); vector cmd; @@ -301,13 +318,13 @@ void launch_debugger(ScopedFd& params_pipe_fd, for (size_t i = 0; i < options.size(); ++i) { if (!did_set_remote && options[i] == "-ex" && i + 1 < options.size() && needs_target(options[i + 1])) { - push_gdb_target_remote_cmd(cmd, host, port); + push_gdb_target_remote_cmd(cmd, socket_domain, host, port); did_set_remote = true; } cmd.push_back(options[i]); } if (!did_set_remote) { - push_gdb_target_remote_cmd(cmd, host, port); + push_gdb_target_remote_cmd(cmd, socket_domain, host, port); } env.push_back("GDB_UNDER_RR=1"); @@ -319,7 +336,7 @@ void launch_debugger(ScopedFd& params_pipe_fd, cmd.push_back("--source-before-file"); cmd.push_back(lldb_command_file); cmd.insert(cmd.end(), options.begin(), options.end()); - push_lldb_target_remote_cmd(cmd, host, port); + push_lldb_target_remote_cmd(cmd, socket_domain, host, port); env.push_back("LLDB_UNDER_RR=1"); break; } @@ -356,8 +373,7 @@ void emergency_debug(Task* t) { // b) some gdb versions will fail if the user doesn't turn off async // mode (and we don't want to require users to do that) features.reverse_execution = false; - unsigned short port = t->tid; - ScopedFd listen_fd = open_socket(localhost_addr, &port, PROBE_PORT); + OpenedSocket listen_socket = open_socket(string(), t->tid, PROBE_PORT); dump_rr_stack(); @@ -369,17 +385,19 @@ void emergency_debug(Task* t) { FILE* gdb_cmd = fopen("gdb_cmd", "w"); if (gdb_cmd) { fputs(to_shell_string( - debugger_launch_command(t, localhost_addr, port, false, "gdb")).c_str(), gdb_cmd); + debugger_launch_command(t, listen_socket.domain, + listen_socket.host, listen_socket.port, false, "gdb")).c_str(), gdb_cmd); fclose(gdb_cmd); } kill(pid, SIGURG); } else { - vector cmd = debugger_launch_command(t, localhost_addr, port, + vector cmd = debugger_launch_command(t, + listen_socket.domain, listen_socket.host, listen_socket.port, false, "gdb"); fprintf(stderr, "Launch debugger with\n %s\n", to_shell_string(cmd).c_str()); } unique_ptr dbg = - GdbServerConnection::await_connection(t, listen_fd, features); + GdbServerConnection::await_connection(t, listen_socket.fd, features); GdbServer::serve_emergency_debugger(std::move(dbg), t); } diff --git a/src/launch_debugger.h b/src/launch_debugger.h index 732568979b7..fc8722cc563 100644 --- a/src/launch_debugger.h +++ b/src/launch_debugger.h @@ -3,6 +3,8 @@ #ifndef RR_LAUNCH_DEBUGGER_H_ #define RR_LAUNCH_DEBUGGER_H_ +#include + #include #include @@ -11,6 +13,13 @@ namespace rr { +struct DebuggerParams { + char exe_image[PATH_MAX]; + int socket_domain; + char host[128]; + short port; +}; + /** * exec()'s the debuger using parameters read from params_pipe_fd. */ @@ -20,7 +29,8 @@ void launch_debugger(ScopedFd& params_pipe_fd, const std::string& debugger_file_ /** * Produces the command line needed to launch the debugger. */ -std::vector debugger_launch_command(Task* t, const std::string& host, +std::vector debugger_launch_command(Task* t, int socket_domain, + const std::string& host, unsigned short port, bool serve_files, const std::string& debugger_name); diff --git a/src/util.cc b/src/util.cc index df0e392753a..854b9154334 100644 --- a/src/util.cc +++ b/src/util.cc @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -1639,16 +1641,58 @@ XSaveLayout xsave_layout_from_trace(const std::vector records) { return layout; } -ScopedFd open_socket(const char* address, unsigned short* port, - ProbePort probe) { - ScopedFd listen_fd(socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0)); +static const char localhost_addr[] = "127.0.0.1"; +static const char localhost_addr_ipv6[] = "::1"; + +// `addr` must be the right size for for the given domain +static bool get_address(int domain, const string& host, struct sockaddr* addr) { + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = domain; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + struct addrinfo* ret; + if (getaddrinfo(host.c_str(), nullptr, &hints, &ret) != 0) { + return false; + } + memcpy(addr, ret->ai_addr, ret->ai_addrlen); + freeaddrinfo(ret); + return true; +} + +OpenedSocket open_socket(const string& host, unsigned short port, + ProbePort probe) { + string host4 = host; + string host6 = host; + if (host.empty()) { + host4 = localhost_addr; + host6 = localhost_addr_ipv6; + } + + struct sockaddr_in addr4; + bool ipv4_ok = get_address(AF_INET, host4, (struct sockaddr*)&addr4); + struct sockaddr_in6 addr6; + bool ipv6_ok = get_address(AF_INET6, host6, (struct sockaddr*)&addr6); + + int domain = -1; + ScopedFd listen_fd; + if (ipv4_ok) { + listen_fd = ScopedFd(socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0)); + if (listen_fd.is_open()) { + domain = AF_INET; + } + } + if (!listen_fd.is_open() && ipv6_ok) { + listen_fd = ScopedFd(socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0)); + if (listen_fd.is_open()) { + domain = AF_INET6; + } + } if (!listen_fd.is_open()) { FATAL() << "Couldn't create socket"; } - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = inet_addr(address); int reuseaddr = 1; int ret = setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)); @@ -1656,37 +1700,61 @@ ScopedFd open_socket(const char* address, unsigned short* port, FATAL() << "Couldn't set SO_REUSEADDR"; } + struct sockaddr* addr; + size_t addr_size; + in_port_t* addr_port; + string* host_out; + if (domain == AF_INET) { + addr = (struct sockaddr*)&addr4; + addr_size = sizeof(addr4); + addr_port = &addr4.sin_port; + addr4.sin_family = AF_INET; + host_out = &host4; + } else { + addr = (struct sockaddr*)&addr6; + addr_size = sizeof(addr6); + addr_port = &addr6.sin6_port; + addr6.sin6_family = AF_INET6; + host_out = &host6; + } + do { - addr.sin_port = htons(*port); - ret = ::bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr)); + *addr_port = htons(port); + ret = ::bind(listen_fd, addr, addr_size); if (ret && probe == PROBE_PORT && (EADDRINUSE == errno || EACCES == errno || EINVAL == errno)) { - *port = 0; + port = 0; continue; } if (ret) { - CLEAN_FATAL() << "Couldn't bind to port " << *port; + CLEAN_FATAL() << "Couldn't bind to port " << port; } ret = listen(listen_fd, 1 /*backlogged connection*/); if (ret && probe == PROBE_PORT && EADDRINUSE == errno) { - *port = 0; + port = 0; continue; } if (ret) { - FATAL() << "Couldn't listen on port " << *port; + FATAL() << "Couldn't listen on port " << port; } - if (*port == 0) { - socklen_t sa_size = sizeof(addr); - ret = getsockname(listen_fd, (struct sockaddr*)&addr, &sa_size); + if (port == 0) { + socklen_t sa_size = addr_size; + ret = getsockname(listen_fd, addr, &sa_size); if (ret) { FATAL() << "Could not get socket port"; } - *port = ntohs(addr.sin_port); + port = ntohs(*addr_port); } break; } while (probe == PROBE_PORT); - return listen_fd; + + OpenedSocket result; + result.fd = std::move(listen_fd); + result.domain = domain; + result.host = *host_out; + result.port = port; + return result; } void notifying_abort() { @@ -2510,8 +2578,6 @@ void replace_in_buffer(MemoryRange src, const uint8_t* src_data, } } -const char localhost_addr[10] = "127.0.0.1"; - void base_name(string& s) { size_t p = s.rfind('/'); if (p != string::npos) { diff --git a/src/util.h b/src/util.h index 533e5eb0b05..9a065d37aa4 100644 --- a/src/util.h +++ b/src/util.h @@ -409,8 +409,22 @@ inline bool is_kernel_trap(int si_code) { enum ProbePort { DONT_PROBE = 0, PROBE_PORT }; -ScopedFd open_socket(const char* address, unsigned short* port, - ProbePort probe); +struct OpenedSocket { + ScopedFd fd; + int domain; + std::string host; + unsigned short port; +}; + +// Open a socket bound to the given address and port. +// If PROBE_PORT is set, probes for a usable port and sets it +// in *port. +// If `host` is empty, binds to localhost. +// Returns the actual bound address, socket domain, and port. +// Selects IPv4 or IPv6 automatically depending on what's in the +// host address and what's available. +OpenedSocket open_socket(const std::string& host, unsigned short port, + ProbePort probe); /** * Like `abort`, but tries to wake up test-monitor for a snapshot if possible. @@ -647,8 +661,6 @@ inline unsigned long long dczid_el0_block_size(void) { void replace_in_buffer(MemoryRange src, const uint8_t* src_data, MemoryRange dst, uint8_t* dst_data); -extern const char localhost_addr[10]; - // Strip any directory part from the filename `s` void base_name(std::string& s);