[Lldb-commits] [lldb] r214031 - llgs: add --reverse-connect support.

Todd Fiala todd.fiala at gmail.com
Sat Jul 26 13:39:17 PDT 2014


Author: tfiala
Date: Sat Jul 26 15:39:17 2014
New Revision: 214031

URL: http://llvm.org/viewvc/llvm-project?rev=214031&view=rev
Log:
llgs: add --reverse-connect support.

Also includes --reverse-connect tests for llgs and debugserver.

Added:
    lldb/trunk/test/tools/lldb-gdbserver/commandline/TestStubReverseConnect.py
Modified:
    lldb/trunk/test/tools/lldb-gdbserver/gdbremote_testcase.py
    lldb/trunk/test/tools/lldb-gdbserver/lldbgdbserverutils.py
    lldb/trunk/tools/lldb-gdbserver/lldb-gdbserver.cpp

Added: lldb/trunk/test/tools/lldb-gdbserver/commandline/TestStubReverseConnect.py
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/test/tools/lldb-gdbserver/commandline/TestStubReverseConnect.py?rev=214031&view=auto
==============================================================================
--- lldb/trunk/test/tools/lldb-gdbserver/commandline/TestStubReverseConnect.py (added)
+++ lldb/trunk/test/tools/lldb-gdbserver/commandline/TestStubReverseConnect.py Sat Jul 26 15:39:17 2014
@@ -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()

Modified: lldb/trunk/test/tools/lldb-gdbserver/gdbremote_testcase.py
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/test/tools/lldb-gdbserver/gdbremote_testcase.py?rev=214031&r1=214030&r2=214031&view=diff
==============================================================================
--- lldb/trunk/test/tools/lldb-gdbserver/gdbremote_testcase.py (original)
+++ lldb/trunk/test/tools/lldb-gdbserver/gdbremote_testcase.py Sat Jul 26 15:39:17 2014
@@ -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: +",

Modified: lldb/trunk/test/tools/lldb-gdbserver/lldbgdbserverutils.py
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/test/tools/lldb-gdbserver/lldbgdbserverutils.py?rev=214031&r1=214030&r2=214031&view=diff
==============================================================================
--- lldb/trunk/test/tools/lldb-gdbserver/lldbgdbserverutils.py (original)
+++ lldb/trunk/test/tools/lldb-gdbserver/lldbgdbserverutils.py Sat Jul 26 15:39:17 2014
@@ -6,7 +6,6 @@ import os.path
 import platform
 import Queue
 import re
-import select
 import socket_packet_pump
 import subprocess
 import time

Modified: lldb/trunk/tools/lldb-gdbserver/lldb-gdbserver.cpp
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/tools/lldb-gdbserver/lldb-gdbserver.cpp?rev=214031&r1=214030&r2=214031&view=diff
==============================================================================
--- lldb/trunk/tools/lldb-gdbserver/lldb-gdbserver.cpp (original)
+++ lldb/trunk/tools/lldb-gdbserver/lldb-gdbserver.cpp Sat Jul 26 15:39:17 2014
@@ -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 (GDBRemoteCommunicationSe
         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 (GDBRemoteCommunicationSe
             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)
-            {
-                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
+            // llgs will connect to the gdb-remote client.
+
+            // Ensure we have a port number for the connection.
+            if (connection_portno == 0)
             {
-                fprintf (stderr, "failed to open named pipe '%s' for writing\n", named_pipe_path);
+                fprintf (stderr, "error: port number must be specified on when using reverse connect");
+                exit (1);
             }
-        }
 
-        // Join the listener thread.
-        if (!JoinListenThread ())
-        {
-            fprintf (stderr, "failed to join the listener thread\n");
-            display_usage (progname);
-            exit (1);
-        }
+            // 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);
+            }
+            if (error.Fail ())
+            {
+                fprintf (stderr, "error: failed to connect to client at '%s': %s", connection_url, error.AsCString ());
+                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 (GDBRemoteCommunicationSe
 
             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 ();
 





More information about the lldb-commits mailing list