diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py --- a/lldb/packages/Python/lldbsuite/test/lldbtest.py +++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -387,7 +387,7 @@ def pid(self): return self._proc.pid - def launch(self, executable, args, extra_env): + def launch(self, executable, args, extra_env, stderr=None): env=None if extra_env: env = dict(os.environ) @@ -395,9 +395,9 @@ self._proc = Popen( [executable] + args, - stdout=open( - os.devnull) if not self._trace_on else None, + stdout=open(os.devnull) if not self._trace_on else None, stdin=PIPE, + stderr=stderr, preexec_fn=lldbplatformutil.enable_attach, env=env) @@ -430,6 +430,9 @@ def wait(self, timeout=None): return self._proc.wait(timeout) + def communicate(self, timeout=None): + return self._proc.communicate(timeout=timeout) + class _RemoteProcess(_BaseProcess): @@ -891,13 +894,14 @@ del p del self.subprocesses[:] - def spawnSubprocess(self, executable, args=[], extra_env=None, install_remote=True): + def spawnSubprocess(self, executable, args=[], extra_env=None, + install_remote=True, **kwargs): """ Creates a subprocess.Popen object with the specified executable and arguments, saves it in self.subprocesses, and returns the object. """ proc = _RemoteProcess( install_remote) if lldb.remote_platform else _LocalProcess(self.TraceOn()) - proc.launch(executable, args, extra_env=extra_env) + proc.launch(executable, args, extra_env=extra_env, **kwargs) self.subprocesses.append(proc) return proc diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py @@ -23,10 +23,6 @@ import logging -class _ConnectionRefused(IOError): - pass - - class GdbRemoteTestCaseFactory(type): def __new__(cls, name, bases, attrs): @@ -133,23 +129,7 @@ self.test_sequence = GdbRemoteTestSequence(self.logger) self.set_inferior_startup_launch() - self.port = self.get_next_port() self.stub_sends_two_stop_notifications_on_kill = False - if configuration.lldb_platform_url: - if configuration.lldb_platform_url.startswith('unix-'): - url_pattern = '(.+)://\[?(.+?)\]?/.*' - else: - url_pattern = '(.+)://(.+):\d+' - scheme, host = re.match( - url_pattern, configuration.lldb_platform_url).groups() - if configuration.lldb_platform_name == 'remote-android' and host != 'localhost': - self.stub_device = host - self.stub_hostname = 'localhost' - else: - self.stub_device = None - self.stub_hostname = host - else: - self.stub_hostname = "localhost" debug_server = self.getDebugServer() if debug_server == "debugserver": @@ -183,19 +163,12 @@ self.debug_monitor_extra_args = [ "--log-file=" + log_file, "--log-flags=0x800000"] - def get_next_port(self): - return 12000 + random.randint(0, 3999) - def reset_test_sequence(self): self.test_sequence = GdbRemoteTestSequence(self.logger) def _init_llgs_test(self): - reverse_connect = True if lldb.remote_platform: - # Reverse connections may be tricky due to firewalls/NATs. - reverse_connect = False - # FIXME: This is extremely linux-oriented # Grab the ppid from /proc/[shell pid]/stat @@ -228,95 +201,14 @@ self.debug_monitor_extra_args = ["gdbserver"] self.setUpServerLogging(is_llgs=True) - self.reverse_connect = reverse_connect - def _init_debugserver_test(self): self.debug_monitor_exe = get_debugserver_exe() self.setUpServerLogging(is_llgs=False) - self.reverse_connect = True # The debugserver stub has a race on handling the 'k' command, so it sends an X09 right away, then sends the real X notification # when the process truly dies. self.stub_sends_two_stop_notifications_on_kill = True - def forward_adb_port(self, source, target, direction, device): - adb = ['adb'] + (['-s', device] if device else []) + [direction] - - def remove_port_forward(): - subprocess.call(adb + ["--remove", "tcp:%d" % source]) - - subprocess.call(adb + ["tcp:%d" % source, "tcp:%d" % target]) - self.addTearDownHook(remove_port_forward) - - def _verify_socket(self, sock): - # Normally, when the remote stub is not ready, we will get ECONNREFUSED during the - # connect() attempt. However, due to the way how ADB forwarding works, on android targets - # the connect() will always be successful, but the connection will be immediately dropped - # if ADB could not connect on the remote side. This function tries to detect this - # situation, and report it as "connection refused" so that the upper layers attempt the - # connection again. - triple = self.dbg.GetSelectedPlatform().GetTriple() - if not re.match(".*-.*-.*-android", triple): - return # Not android. - can_read, _, _ = select.select([sock], [], [], 0.1) - if sock not in can_read: - return # Data is not available, but the connection is alive. - if len(sock.recv(1, socket.MSG_PEEK)) == 0: - raise _ConnectionRefused() # Got EOF, connection dropped. - - def create_socket(self): - try: - sock = socket.socket(family=socket.AF_INET) - except OSError as e: - if e.errno != errno.EAFNOSUPPORT: - raise - sock = socket.socket(family=socket.AF_INET6) - - logger = self.logger - - triple = self.dbg.GetSelectedPlatform().GetTriple() - if re.match(".*-.*-.*-android", triple): - self.forward_adb_port( - self.port, - self.port, - "forward", - self.stub_device) - - logger.info( - "Connecting to debug monitor on %s:%d", - self.stub_hostname, - self.port) - connect_info = (self.stub_hostname, self.port) - try: - sock.connect(connect_info) - except socket.error as serr: - if serr.errno == errno.ECONNREFUSED: - raise _ConnectionRefused() - raise serr - - def shutdown_socket(): - if sock: - try: - # send the kill packet so lldb-server shuts down gracefully - sock.sendall(GdbRemoteTestCaseBase._GDBREMOTE_KILL_PACKET) - except: - logger.warning( - "failed to send kill packet to debug monitor: {}; ignoring".format( - sys.exc_info()[0])) - - try: - sock.close() - except: - logger.warning( - "failed to close socket to debug monitor: {}; ignoring".format( - sys.exc_info()[0])) - - self.addTearDownHook(shutdown_socket) - - self._verify_socket(sock) - - return sock - def set_inferior_startup_launch(self): self._inferior_startup = self._STARTUP_LAUNCH @@ -330,14 +222,7 @@ commandline_args = self.debug_monitor_extra_args if attach_pid: commandline_args += ["--attach=%d" % attach_pid] - if self.reverse_connect: - commandline_args += ["--reverse-connect", self.connect_address] - else: - if lldb.remote_platform: - commandline_args += ["*:{}".format(self.port)] - else: - commandline_args += ["localhost:{}".format(self.port)] - + commandline_args += ["--reverse-connect", self.connect_address] return commandline_args def get_target_byte_order(self): @@ -346,16 +231,14 @@ return target.GetByteOrder() def launch_debug_monitor(self, attach_pid=None, logfile=None): - if self.reverse_connect: - family, type, proto, _, addr = socket.getaddrinfo("localhost", 0, proto=socket.IPPROTO_TCP)[0] - sock = socket.socket(family, type, proto) - sock.settimeout(self.DEFAULT_TIMEOUT) - - sock.bind(addr) - sock.listen(1) - addr = sock.getsockname() - self.connect_address = "[{}]:{}".format(*addr) + family, type, proto, _, addr = socket.getaddrinfo("localhost", 0, proto=socket.IPPROTO_TCP)[0] + sock = socket.socket(family, type, proto) + sock.settimeout(self.DEFAULT_TIMEOUT) + sock.bind(addr) + sock.listen(1) + addr = sock.getsockname() + self.connect_address = "[{}]:{}".format(*addr) # Create the command line. commandline_args = self.get_debug_monitor_command_line_args( @@ -368,70 +251,21 @@ install_remote=False) self.assertIsNotNone(server) - if self.reverse_connect: - self.sock = sock.accept()[0] - self.sock.settimeout(self.DEFAULT_TIMEOUT) + self.sock = sock.accept()[0] + self.sock.settimeout(self.DEFAULT_TIMEOUT) return server def connect_to_debug_monitor(self, attach_pid=None): - if self.reverse_connect: - # Create the stub. - server = self.launch_debug_monitor(attach_pid=attach_pid) - self.assertIsNotNone(server) - - # Schedule debug monitor to be shut down during teardown. - logger = self.logger - - self._server = Server(self.sock, server) - return server - - # We're using a random port algorithm to try not to collide with other ports, - # and retry a max # times. - attempts = 0 - MAX_ATTEMPTS = 20 - - while attempts < MAX_ATTEMPTS: - server = self.launch_debug_monitor(attach_pid=attach_pid) - - # Schedule debug monitor to be shut down during teardown. - logger = self.logger - - connect_attemps = 0 - MAX_CONNECT_ATTEMPTS = 10 - - while connect_attemps < MAX_CONNECT_ATTEMPTS: - # Create a socket to talk to the server - try: - logger.info("Connect attempt %d", connect_attemps + 1) - self.sock = self.create_socket() - self._server = Server(self.sock, server) - return server - except _ConnectionRefused as serr: - # Ignore, and try again. - pass - time.sleep(0.5) - connect_attemps += 1 - - # We should close the server here to be safe. - server.terminate() - - # Increment attempts. - print( - "connect to debug monitor on port %d failed, attempt #%d of %d" % - (self.port, attempts + 1, MAX_ATTEMPTS)) - attempts += 1 - - # And wait a random length of time before next attempt, to avoid - # collisions. - time.sleep(random.randint(1, 5)) - - # Now grab a new port number. - self.port = self.get_next_port() - - raise Exception( - "failed to create a socket to the launched debug monitor after %d tries" % - attempts) + # Create the stub. + server = self.launch_debug_monitor(attach_pid=attach_pid) + self.assertIsNotNone(server) + + # Schedule debug monitor to be shut down during teardown. + logger = self.logger + + self._server = Server(self.sock, server) + return server def launch_process_for_attach( self, diff --git a/lldb/test/API/tools/lldb-server/TestGdbRemoteCompletion.py b/lldb/test/API/tools/lldb-server/TestGdbRemoteCompletion.py --- a/lldb/test/API/tools/lldb-server/TestGdbRemoteCompletion.py +++ b/lldb/test/API/tools/lldb-server/TestGdbRemoteCompletion.py @@ -4,10 +4,14 @@ from lldbsuite.test.decorators import * from lldbgdbserverutils import * +@skipIfRemote class GdbRemoteCompletionTestCase(gdbremote_testcase.GdbRemoteTestCaseBase): mydir = TestBase.compute_mydir(__file__) def init_lldb_server(self): + family, type, proto, _, addr = socket.getaddrinfo( + "localhost", 0, proto=socket.IPPROTO_TCP)[0] + self.debug_monitor_exe = get_lldb_server_exe() if not self.debug_monitor_exe: self.skipTest("lldb-server exe not found") @@ -15,7 +19,7 @@ commandline_args = [ "platform", "--listen", - "*:0", + "[{}]:{}".format(*addr), "--socket-file", port_file ] @@ -24,9 +28,12 @@ commandline_args, install_remote=False) self.assertIsNotNone(server) - self.stub_hostname = "localhost" - self.port = int(lldbutil.wait_for_file_on_target(self, port_file)) - self.sock = self.create_socket() + port = int(lldbutil.wait_for_file_on_target(self, port_file)) + + self.sock = socket.socket(family, type, proto) + self.sock.settimeout(self.DEFAULT_TIMEOUT) + addr = (addr[0], port) + self.sock.connect(addr) self._server = Server(self.sock, server) self.do_handshake() diff --git a/lldb/test/API/tools/lldb-server/TestPtyServer.py b/lldb/test/API/tools/lldb-server/TestPtyServer.py --- a/lldb/test/API/tools/lldb-server/TestPtyServer.py +++ b/lldb/test/API/tools/lldb-server/TestPtyServer.py @@ -8,6 +8,7 @@ @skipIfWindows +@skipIfRemote class PtyServerTestCase(gdbremote_testcase.GdbRemoteTestCaseBase): mydir = TestBase.compute_mydir(__file__) @@ -20,21 +21,21 @@ self._primary = io.FileIO(primary, 'r+b') self._secondary = io.FileIO(secondary, 'r+b') - def get_debug_monitor_command_line_args(self, attach_pid=None): - commandline_args = self.debug_monitor_extra_args - if attach_pid: - commandline_args += ["--attach=%d" % attach_pid] + def connect_to_debug_monitor(self, attach_pid=None): + self.assertIsNone(attach_pid) + # Create the command line. libc = ctypes.CDLL(None) libc.ptsname.argtypes = (ctypes.c_int,) libc.ptsname.restype = ctypes.c_char_p pty_path = libc.ptsname(self._primary.fileno()).decode() - commandline_args += ["serial://%s" % (pty_path,)] - return commandline_args + commandline_args = self.debug_monitor_extra_args + ["serial://%s" % (pty_path,)] - def connect_to_debug_monitor(self, attach_pid=None): - self.reverse_connect = False - server = self.launch_debug_monitor(attach_pid=attach_pid) + # Start the server. + server = self.spawnSubprocess( + self.debug_monitor_exe, + commandline_args, + install_remote=False) self.assertIsNotNone(server) # TODO: make it into proper abstraction diff --git a/lldb/test/API/tools/lldb-server/commandline/TestGdbRemoteConnection.py b/lldb/test/API/tools/lldb-server/commandline/TestGdbRemoteConnection.py --- a/lldb/test/API/tools/lldb-server/commandline/TestGdbRemoteConnection.py +++ b/lldb/test/API/tools/lldb-server/commandline/TestGdbRemoteConnection.py @@ -128,7 +128,6 @@ mydir = TestBase.compute_mydir(__file__) - @skipIfRemote # reverse connect is not a supported use case for now def test_reverse_connect(self): # Reverse connect is the default connection method. self.connect_to_debug_monitor() @@ -138,7 +137,7 @@ @skipIfRemote def test_named_pipe(self): family, type, proto, _, addr = socket.getaddrinfo( - self.stub_hostname, 0, proto=socket.IPPROTO_TCP)[0] + "localhost", 0, proto=socket.IPPROTO_TCP)[0] self.sock = socket.socket(family, type, proto) self.sock.settimeout(self.DEFAULT_TIMEOUT) @@ -149,11 +148,7 @@ self.addTearDownHook(lambda: pipe.close()) args = self.debug_monitor_extra_args - if lldb.remote_platform: - args += ["*:0"] - else: - args += ["localhost:0"] - + args += ["localhost:0"] args += ["--named-pipe", pipe.name] server = self.spawnSubprocess( @@ -170,3 +165,40 @@ # Verify we can do the handshake. If that works, we'll call it good. self.do_handshake() + + @skipIfRemote + def test_forward_connect(self): + family, type, proto, _, addr = socket.getaddrinfo( + "localhost", 0, proto=socket.IPPROTO_TCP)[0] + self.sock = socket.socket(family, type, proto) + self.sock.settimeout(self.DEFAULT_TIMEOUT) + + self.addTearDownHook(lambda: self.sock.close()) + + for i in range(11): + if i == 10: + self.fail("Failed to connect after 10 attempts") + args = [] + self.debug_monitor_extra_args + port = random.randint(5000, 65000) + addr = (addr[0], port) + args += ["[{}]:{}".format(*addr)] + + server = self.spawnSubprocess(self.debug_monitor_exe, args, + stderr=subprocess.PIPE) + + try: + (_, err) = server.communicate(timeout=10) + # The server (unexpectedly) exited. If that happened because the + # port was taken, try again. + self.assertIn(b"failed to connect", err) + continue + except subprocess.TimeoutExpired: + # Good. The server is waiting for us to connect. + break + self.assertIsNotNone(server) + + self.sock.connect(addr) + self._server = Server(self.sock, server) + + # Verify we can do the handshake. If that works, we'll call it good. + self.do_handshake()