diff options
| author | Todd Fiala <todd.fiala@gmail.com> | 2014-07-26 20:39:17 +0000 |
|---|---|---|
| committer | Todd Fiala <todd.fiala@gmail.com> | 2014-07-26 20:39:17 +0000 |
| commit | 31bde322f374582d7106f0c847b0ff3b6b6d705b (patch) | |
| tree | e19a633d1e7fdd6eaf5d859adc0320e4073f6f9e | |
| parent | 7b70cadae92a17b6d96cd180c96bb9e83e35d705 (diff) | |
| download | llvm-31bde322f374582d7106f0c847b0ff3b6b6d705b.zip llvm-31bde322f374582d7106f0c847b0ff3b6b6d705b.tar.gz llvm-31bde322f374582d7106f0c847b0ff3b6b6d705b.tar.bz2 | |
llgs: add --reverse-connect support.
Also includes --reverse-connect tests for llgs and debugserver.
llvm-svn: 214031
4 files changed, 227 insertions, 52 deletions
diff --git a/lldb/test/tools/lldb-gdbserver/commandline/TestStubReverseConnect.py b/lldb/test/tools/lldb-gdbserver/commandline/TestStubReverseConnect.py new file mode 100644 index 0000000..642cf19a --- /dev/null +++ b/lldb/test/tools/lldb-gdbserver/commandline/TestStubReverseConnect.py @@ -0,0 +1,86 @@ +# Add the directory above ours to the python library path since we +# will import from there. +import os.path +import sys +sys.path.append(os.path.join(os.path.dirname(__file__), "..")) + +import gdbremote_testcase +import re +import select +import socket +import time +from lldbtest import * + +class TestStubReverseConnect(gdbremote_testcase.GdbRemoteTestCaseBase): + _DEFAULT_TIMEOUT = 20 + + def setUp(self): + # Set up the test. + gdbremote_testcase.GdbRemoteTestCaseBase.setUp(self) + + # Create a listener on a local port. + self.listener_socket = self.create_listener_socket() + self.assertIsNotNone(self.listener_socket) + self.listener_port = self.listener_socket.getsockname()[1] + + def create_listener_socket(self, timeout_seconds=_DEFAULT_TIMEOUT): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.assertIsNotNone(sock) + + sock.settimeout(timeout_seconds) + sock.bind(("127.0.0.1",0)) + sock.listen(1) + + def tear_down_listener(): + try: + sock.shutdown(socket.SHUT_RDWR) + except: + # ignore + None + + self.addTearDownHook(tear_down_listener) + return sock + + def reverse_connect_works(self): + # Indicate stub startup should do a reverse connect. + appended_stub_args = " --reverse-connect" + if self.debug_monitor_extra_args: + self.debug_monitor_extra_args += appended_stub_args + else: + self.debug_monitor_extra_args = appended_stub_args + + self.stub_hostname = "127.0.0.1" + self.port = self.listener_port + + # Start the stub. + server = self.launch_debug_monitor(logfile=sys.stdout) + self.assertIsNotNone(server) + self.assertTrue(server.isalive()) + + # Listen for the stub's connection to us. + (stub_socket, address) = self.listener_socket.accept() + self.assertIsNotNone(stub_socket) + self.assertIsNotNone(address) + print "connected to stub {} on {}".format(address, stub_socket.getsockname()) + + # Verify we can do the handshake. If that works, we'll call it good. + self.do_handshake(stub_socket, timeout_seconds=self._DEFAULT_TIMEOUT) + + # Clean up. + stub_socket.shutdown(socket.SHUT_RDWR) + + @debugserver_test + def test_reverse_connect_works_debugserver(self): + self.init_debugserver_test(use_named_pipe=False) + self.set_inferior_startup_launch() + self.reverse_connect_works() + + @llgs_test + def test_reverse_connect_works_llgs(self): + self.init_llgs_test(use_named_pipe=False) + self.set_inferior_startup_launch() + self.reverse_connect_works() + + +if __name__ == '__main__': + unittest2.main() diff --git a/lldb/test/tools/lldb-gdbserver/gdbremote_testcase.py b/lldb/test/tools/lldb-gdbserver/gdbremote_testcase.py index a079358..8c473d5 100644 --- a/lldb/test/tools/lldb-gdbserver/gdbremote_testcase.py +++ b/lldb/test/tools/lldb-gdbserver/gdbremote_testcase.py @@ -8,6 +8,7 @@ import os.path import platform import random import re +import select import sets import signal import socket @@ -55,6 +56,7 @@ class GdbRemoteTestCaseBase(TestBase): self.named_pipe = None self.named_pipe_fd = None self.stub_sends_two_stop_notifications_on_kill = False + self.stub_hostname = "localhost" def get_next_port(self): return 12000 + random.randint(0,3999) @@ -165,7 +167,7 @@ class GdbRemoteTestCaseBase(TestBase): self.addTearDownHook(shutdown_socket) - connect_info = ("localhost", self.port) + connect_info = (self.stub_hostname, self.port) # print "connecting to stub on {}:{}".format(connect_info[0], connect_info[1]) sock.connect(connect_info) @@ -177,17 +179,21 @@ class GdbRemoteTestCaseBase(TestBase): def set_inferior_startup_attach(self): self._inferior_startup = self._STARTUP_ATTACH - def launch_debug_monitor(self, attach_pid=None): - # Create the command line. - import pexpect + def get_debug_monitor_command_line(self, attach_pid=None): commandline = "{}{} localhost:{}".format(self.debug_monitor_exe, self.debug_monitor_extra_args, self.port) if attach_pid: commandline += " --attach=%d" % attach_pid if self.named_pipe_path: commandline += " --named-pipe %s" % self.named_pipe_path + return commandline + + def launch_debug_monitor(self, attach_pid=None, logfile=None): + # Create the command line. + import pexpect + commandline = self.get_debug_monitor_command_line(attach_pid=attach_pid) # Start the server. - server = pexpect.spawn(commandline) + server = pexpect.spawn(commandline, logfile=logfile) self.assertIsNotNone(server) server.expect(r"(debugserver|lldb-gdbserver)", timeout=10) @@ -332,6 +338,45 @@ class GdbRemoteTestCaseBase(TestBase): return {"inferior":inferior, "server":server} + def expect_socket_recv(self, sock, expected_content_regex, timeout_seconds): + response = "" + timeout_time = time.time() + timeout_seconds + + while not expected_content_regex.match(response) and time.time() < timeout_time: + can_read, _, _ = select.select([sock], [], [], timeout_seconds) + if can_read and sock in can_read: + recv_bytes = sock.recv(4096) + if recv_bytes: + response += recv_bytes + + self.assertTrue(expected_content_regex.match(response)) + + def expect_socket_send(self, sock, content, timeout_seconds): + request_bytes_remaining = content + timeout_time = time.time() + timeout_seconds + + while len(request_bytes_remaining) > 0 and time.time() < timeout_time: + _, can_write, _ = select.select([], [sock], [], timeout_seconds) + if can_write and sock in can_write: + written_byte_count = sock.send(request_bytes_remaining) + request_bytes_remaining = request_bytes_remaining[written_byte_count:] + self.assertEquals(len(request_bytes_remaining), 0) + + def do_handshake(self, stub_socket, timeout_seconds=5): + # Write the ack. + self.expect_socket_send(stub_socket, "+", timeout_seconds) + + # Send the start no ack mode packet. + NO_ACK_MODE_REQUEST = "$QStartNoAckMode#b0" + bytes_sent = stub_socket.send(NO_ACK_MODE_REQUEST) + self.assertEquals(bytes_sent, len(NO_ACK_MODE_REQUEST)) + + # Receive the ack and "OK" + self.expect_socket_recv(stub_socket, re.compile(r"^\+\$OK#[0-9a-fA-F]{2}$"), timeout_seconds) + + # Send the final ack. + self.expect_socket_send(stub_socket, "+", timeout_seconds) + def add_no_ack_remote_stream(self): self.test_sequence.add_log_lines( ["read packet: +", diff --git a/lldb/test/tools/lldb-gdbserver/lldbgdbserverutils.py b/lldb/test/tools/lldb-gdbserver/lldbgdbserverutils.py index d89107e..7ba0426 100644 --- a/lldb/test/tools/lldb-gdbserver/lldbgdbserverutils.py +++ b/lldb/test/tools/lldb-gdbserver/lldbgdbserverutils.py @@ -6,7 +6,6 @@ import os.path import platform import Queue import re -import select import socket_packet_pump import subprocess import time diff --git a/lldb/tools/lldb-gdbserver/lldb-gdbserver.cpp b/lldb/tools/lldb-gdbserver/lldb-gdbserver.cpp index db31d33..db6077a 100644 --- a/lldb/tools/lldb-gdbserver/lldb-gdbserver.cpp +++ b/lldb/tools/lldb-gdbserver/lldb-gdbserver.cpp @@ -53,9 +53,9 @@ using namespace lldb_private; namespace { - static lldb::thread_t s_listen_thread = LLDB_INVALID_HOST_THREAD; - static std::unique_ptr<ConnectionFileDescriptor> s_listen_connection_up; - static std::string s_listen_url; + lldb::thread_t s_listen_thread = LLDB_INVALID_HOST_THREAD; + std::unique_ptr<ConnectionFileDescriptor> s_listen_connection_up; + std::string s_listen_url; } //---------------------------------------------------------------------- @@ -76,6 +76,7 @@ static struct option g_long_options[] = { "attach", required_argument, NULL, 'a' }, { "named-pipe", required_argument, NULL, 'P' }, { "native-regs", no_argument, NULL, 'r' }, // Specify to use the native registers instead of the gdb defaults for the architecture. NOTE: this is a do-nothing arg as it's behavior is default now. FIXME remove call from lldb-platform. + { "reverse-connect", no_argument, NULL, 'R' }, // Specifies that llgs attaches to the client address:port rather than llgs listening for a connection from address on port. { "setsid", no_argument, NULL, 'S' }, // Call setsid() to make llgs run in its own session. { NULL, 0, NULL, 0 } }; @@ -322,16 +323,17 @@ JoinListenThread () } void -start_listener (GDBRemoteCommunicationServer &gdb_server, const char *const host_and_port, const char *const progname, const char *const named_pipe_path) +ConnectToRemote (GDBRemoteCommunicationServer &gdb_server, bool reverse_connect, const char *const host_and_port, const char *const progname, const char *const named_pipe_path) { Error error; if (host_and_port && host_and_port[0]) { + // Parse out host and port. std::string final_host_and_port; - std::string listening_host; - std::string listening_port; - uint32_t listening_portno = 0; + std::string connection_host; + std::string connection_port; + uint32_t connection_portno = 0; // If host_and_port starts with ':', default the host to be "localhost" and expect the remainder to be the port. if (host_and_port[0] == ':') @@ -341,9 +343,9 @@ start_listener (GDBRemoteCommunicationServer &gdb_server, const char *const host const std::string::size_type colon_pos = final_host_and_port.find (':'); if (colon_pos != std::string::npos) { - listening_host = final_host_and_port.substr (0, colon_pos); - listening_port = final_host_and_port.substr (colon_pos + 1); - listening_portno = Args::StringToUInt32 (listening_port.c_str (), 0); + connection_host = final_host_and_port.substr (0, colon_pos); + connection_port = final_host_and_port.substr (colon_pos + 1); + connection_portno = Args::StringToUInt32 (connection_port.c_str (), 0); } else { @@ -352,51 +354,89 @@ start_listener (GDBRemoteCommunicationServer &gdb_server, const char *const host exit (1); } - // Start the listener on a new thread. We need to do this so we can resolve the - // bound listener port. - StartListenThread(listening_host.c_str (), static_cast<uint16_t> (listening_portno)); - printf ("Listening to port %s for a connection from %s...\n", listening_port.c_str (), listening_host.c_str ()); - - // If we have a named pipe to write the port number back to, do that now. - if (named_pipe_path && named_pipe_path[0] && listening_portno == 0) + if (reverse_connect) { - // FIXME use new generic named pipe support. - int fd = ::open(named_pipe_path, O_WRONLY); - if (fd > -1) + // llgs will connect to the gdb-remote client. + + // Ensure we have a port number for the connection. + if (connection_portno == 0) { - const uint16_t bound_port = s_listen_connection_up->GetBoundPort (10); + fprintf (stderr, "error: port number must be specified on when using reverse connect"); + exit (1); + } - char port_str[64]; - const ssize_t port_str_len = ::snprintf (port_str, sizeof(port_str), "%u", bound_port); - // Write the port number as a C string with the NULL terminator. - ::write (fd, port_str, port_str_len + 1); - close (fd); + // Build the connection string. + char connection_url[512]; + snprintf(connection_url, sizeof(connection_url), "connect://%s", final_host_and_port.c_str ()); + + // Create the connection. + std::unique_ptr<ConnectionFileDescriptor> connection_up (new ConnectionFileDescriptor ()); + connection_up.reset (new ConnectionFileDescriptor ()); + auto connection_result = connection_up->Connect (connection_url, &error); + if (connection_result != eConnectionStatusSuccess) + { + fprintf (stderr, "error: failed to connect to client at '%s' (connection status: %d)", connection_url, static_cast<int> (connection_result)); + exit (-1); } - else + if (error.Fail ()) { - fprintf (stderr, "failed to open named pipe '%s' for writing\n", named_pipe_path); + fprintf (stderr, "error: failed to connect to client at '%s': %s", connection_url, error.AsCString ()); + exit (-1); } - } - // Join the listener thread. - if (!JoinListenThread ()) - { - fprintf (stderr, "failed to join the listener thread\n"); - display_usage (progname); - exit (1); - } - - // Ensure we connected. - if (s_listen_connection_up) - { + // We're connected. printf ("Connection established.\n"); - gdb_server.SetConnection (s_listen_connection_up.release()); + gdb_server.SetConnection (connection_up.release()); } else { - fprintf (stderr, "failed to connect to '%s': %s\n", final_host_and_port.c_str (), error.AsCString ()); - display_usage (progname); - exit (1); + // llgs will listen for connections on the given port from the given address. + // Start the listener on a new thread. We need to do this so we can resolve the + // bound listener port. + StartListenThread(connection_host.c_str (), static_cast<uint16_t> (connection_portno)); + printf ("Listening to port %s for a connection from %s...\n", connection_port.c_str (), connection_host.c_str ()); + + // If we have a named pipe to write the port number back to, do that now. + if (named_pipe_path && named_pipe_path[0] && connection_portno == 0) + { + // FIXME use new generic named pipe support. + int fd = ::open(named_pipe_path, O_WRONLY); + if (fd > -1) + { + const uint16_t bound_port = s_listen_connection_up->GetBoundPort (10); + + char port_str[64]; + const ssize_t port_str_len = ::snprintf (port_str, sizeof(port_str), "%u", bound_port); + // Write the port number as a C string with the NULL terminator. + ::write (fd, port_str, port_str_len + 1); + close (fd); + } + else + { + fprintf (stderr, "failed to open named pipe '%s' for writing\n", named_pipe_path); + } + } + + // Join the listener thread. + if (!JoinListenThread ()) + { + fprintf (stderr, "failed to join the listener thread\n"); + display_usage (progname); + exit (1); + } + + // Ensure we connected. + if (s_listen_connection_up) + { + printf ("Connection established.\n"); + gdb_server.SetConnection (s_listen_connection_up.release()); + } + else + { + fprintf (stderr, "failed to connect to '%s': %s\n", final_host_and_port.c_str (), error.AsCString ()); + display_usage (progname); + exit (1); + } } } @@ -411,7 +451,7 @@ start_listener (GDBRemoteCommunicationServer &gdb_server, const char *const host bool interrupt = false; bool done = false; - while (!interrupt && !done && (g_sighup_received_count < 1)) + while (!interrupt && !done && (g_sighup_received_count < 2)) { const GDBRemoteCommunication::PacketResult result = gdb_server.GetPacketAndSendResponse (TIMEOUT_USEC, error, interrupt, done); if ((result != GDBRemoteCommunication::PacketResult::Success) && @@ -462,6 +502,7 @@ main (int argc, char *argv[]) std::string platform_name; std::string attach_target; std::string named_pipe_path; + bool reverse_connect = false; initialize_lldb_gdbserver (); @@ -547,6 +588,10 @@ main (int argc, char *argv[]) // Do nothing, native regs is the default these days break; + case 'R': + reverse_connect = true; + break; + #ifndef _WIN32 case 'S': // Put llgs into a new session. Terminals group processes @@ -631,7 +676,7 @@ main (int argc, char *argv[]) // Print version info. printf("%s-%s", LLGS_PROGRAM_NAME, LLGS_VERSION_STR); - start_listener (gdb_server, host_and_port, progname, named_pipe_path.c_str ()); + ConnectToRemote (gdb_server, reverse_connect, host_and_port, progname, named_pipe_path.c_str ()); terminate_lldb_gdbserver (); |
