Repository: microsoft/ptvsd

Issue:
Support for attach to pid in the new adapter



Discussion:

Resolving patch:
diff --git a/src/ptvsd/adapter/debuggee.py b/src/ptvsd/adapter/debuggee.py
--- 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
--- a/src/ptvsd/adapter/messages.py
+++ b/src/ptvsd/adapter/messages.py
@@ -199,9 +199,12 @@ def attach_request(self, request):
         _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)
 


Test patch:
diff --git a/tests/ptvsd/common/test_launcher.py b/tests/ptvsd/common/test_launcher.py
--- a/tests/ptvsd/common/test_launcher.py
+++ b/tests/ptvsd/common/test_launcher.py
@@ -8,6 +8,7 @@
 import os.path
 import platform
 import pytest
+import socket
 import subprocess
 import sys
 
@@ -17,10 +18,31 @@
 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 code_to_run():
 
     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 code_to_run():
             stdout=subprocess.PIPE,
         )
 
+    assert pid_server.wait_for_pid() >= -1
+
     if wait_for_user:
         outstr = b""
         while not outstr.endswith(b". . . "):