diff --git a/src/ptvsd/adapter/debuggee.py b/src/ptvsd/adapter/debuggee.py index 82b36755..9aafdaa4 100644 --- a/src/ptvsd/adapter/debuggee.py +++ b/src/ptvsd/adapter/debuggee.py @@ -80,6 +80,48 @@ def spawn_and_connect(request): before_accept=lambda address: _parse_request_and_spawn(request, address), ) +def attach_by_pid(request): + """Start server to receive connection from the debug server injected into the + debuggee process. + """ + def _parse_request_and_inject(request, address): + cmdline = [sys.executable] + + host, port = address + ptvsd_args = request("ptvsdArgs", json.array(unicode)) + cmdline += [ + compat.filename(ptvsd.__main__.__file__), + "--client", + "--host", + host, + "--port", + str(port), + "--pid", + str(request("processId", int)) + ] + ptvsd_args + + log.debug("Launching debugger injector: {0!r}", cmdline) + + try: + # This process will immediately exit after injecting debug server + proc = subprocess.Popen( + cmdline, + bufsize=0, + ) + except Exception as exc: + raise request.cant_handle("Error launching debug process: {0}", exc) + proc.wait() + if proc.returncode != 0: + raise request.cant_handle( + "Failed to inject debugger with error code: {0}", + proc.returncode, + ) + + channels.Channels().accept_connection_from_server( + ("127.0.0.1", 0), + before_accept=lambda address: _parse_request_and_inject(address), + ) + def _parse_request_and_spawn(request, address): spawn_info = _parse_request(request, address) diff --git a/src/ptvsd/adapter/messages.py b/src/ptvsd/adapter/messages.py index 23146831..74229e45 100644 --- a/src/ptvsd/adapter/messages.py +++ b/src/ptvsd/adapter/messages.py @@ -199,9 +199,12 @@ class IDEMessages(Messages): _Shared.readonly_attrs.add("terminate_on_disconnect") self._debug_config(request) - options.host = request("host", options.host) - options.port = request("port", options.port) - _channels.connect_to_server(address=(options.host, options.port)) + if "processId" in request: + debuggee.attach_by_pid(request) + else: + options.host = request("host", options.host) + options.port = request("port", options.port) + _channels.connect_to_server(address=(options.host, options.port)) return self._configure(request) diff --git a/tests/ptvsd/common/test_launcher.py b/tests/ptvsd/common/test_launcher.py index d5881375..bf23ed26 100644 --- a/tests/ptvsd/common/test_launcher.py +++ b/tests/ptvsd/common/test_launcher.py @@ -8,6 +8,7 @@ import errno import os.path import platform import pytest +import socket import subprocess import sys @@ -17,10 +18,31 @@ from ptvsd.common import launcher launcher_py = os.path.abspath(launcher.__file__) +class ReceivePid(object): + def start_server(self): + self.listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.listener.bind(("127.0.0.1", 0)) + self.listener.listen(1) + self.host, self.port = self.listener.getsockname() + return (self.host, self.port) + + def wait_for_pid(self): + try: + sock, _ = self.listener.accept() + finally: + self.listener.close() + try: + data = sock.makefile().read() + finally: + sock.close() + return -1 if data == b"" else int(data) + + @pytest.mark.parametrize("run_as", ["program", "module", "code"]) @pytest.mark.parametrize("mode", ["normal", "abnormal", "normal+abnormal", ""]) @pytest.mark.parametrize("seperator", ["seperator", ""]) -def test_launcher_parser(mode, seperator, run_as): +@pytest.mark.parametrize("port", ["12345", ""]) +def test_launcher_parser(mode, seperator, run_as, port): args = [] switch = mode.split("+") @@ -31,9 +53,13 @@ def test_launcher_parser(mode, seperator, run_as): if "abnormal" in switch: args += [launcher.WAIT_ON_ABNORMAL_SWITCH] + if port: + args += [launcher.INTERNAL_PORT_SWITCH, port] + if seperator: args += ["--"] + if run_as == "file": expected = ["myscript.py", "--arg1", "--arg2", "--arg3", "--", "more args"] elif run_as == "module": @@ -67,12 +93,17 @@ def test_launcher(pyfile, mode, exit_code, run_as): switch = mode.split("+") + pid_server = ReceivePid() + _, port = pid_server.start_server() + if "normal" in switch: args += [launcher.WAIT_ON_NORMAL_SWITCH] if "abnormal" in switch: args += [launcher.WAIT_ON_ABNORMAL_SWITCH] + args += [launcher.INTERNAL_PORT_SWITCH, str(port)] + args += ["--"] if run_as == "file": @@ -110,6 +141,8 @@ def test_launcher(pyfile, mode, exit_code, run_as): stdout=subprocess.PIPE, ) + assert pid_server.wait_for_pid() >= -1 + if wait_for_user: outstr = b"" while not outstr.endswith(b". . . "):