mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
enable_attach should spawn adapter (#1784)
* Spawn adapter in enable attach. * Addressing comments and simplifying * Minor tweaks
This commit is contained in:
parent
0825dbeb37
commit
636400a24a
7 changed files with 122 additions and 45 deletions
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
|
|
@ -5,13 +5,13 @@
|
|||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "ptvsd.adapter --debug-server",
|
||||
"name": "ptvsd.adapter --port",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"console": "integratedTerminal",
|
||||
"consoleTitle": "ptvsd.adapter",
|
||||
"program": "${workspaceFolder}/src/ptvsd/adapter",
|
||||
"args": ["--debug-server", "8765", "--cls"],
|
||||
"args": ["--port", "8765", "--cls"],
|
||||
"customDebugger": true,
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ from _pydev_imps._pydev_saved_modules import thread
|
|||
from _pydev_imps._pydev_saved_modules import threading
|
||||
from socket import AF_INET, SOCK_STREAM, SHUT_RD, SHUT_WR, SOL_SOCKET, SO_REUSEADDR, SHUT_RDWR, IPPROTO_TCP
|
||||
from _pydevd_bundle.pydevd_constants import (DebugInfoHolder, get_thread_id, IS_WINDOWS, IS_JYTHON,
|
||||
IS_PY2, IS_PY36_OR_GREATER, STATE_RUN, dict_keys, ASYNC_EVAL_TIMEOUT_SEC, GlobalDebuggerHolder,
|
||||
IS_PY2, IS_PY36_OR_GREATER, STATE_RUN, dict_keys, ASYNC_EVAL_TIMEOUT_SEC,
|
||||
get_global_debugger, GetGlobalDebugger, set_global_debugger) # Keep for backward compatibility @UnusedImport
|
||||
from _pydev_bundle.pydev_override import overrides
|
||||
import weakref
|
||||
|
|
@ -187,7 +187,7 @@ def run_as_pydevd_daemon_thread(func, *args, **kwargs):
|
|||
class ReaderThread(PyDBDaemonThread):
|
||||
''' reader thread reads and dispatches commands in an infinite loop '''
|
||||
|
||||
def __init__(self, sock, terminate_on_socket_close=True):
|
||||
def __init__(self, sock, py_db, terminate_on_socket_close=True):
|
||||
assert sock is not None
|
||||
from _pydevd_bundle.pydevd_process_net_command_json import PyDevJsonCommandProcessor
|
||||
from _pydevd_bundle.pydevd_process_net_command import process_net_command
|
||||
|
|
@ -195,22 +195,17 @@ class ReaderThread(PyDBDaemonThread):
|
|||
self._terminate_on_socket_close = terminate_on_socket_close
|
||||
|
||||
self.sock = sock
|
||||
self.py_db = py_db
|
||||
self._buffer = b''
|
||||
self.setName("pydevd.Reader")
|
||||
self.process_net_command = process_net_command
|
||||
self.process_net_command_json = PyDevJsonCommandProcessor(self._from_json).process_net_command_json
|
||||
self.global_debugger_holder = GlobalDebuggerHolder
|
||||
|
||||
self._dap_messages_listeners = []
|
||||
|
||||
def _from_json(self, json_msg, update_ids_from_dap=False):
|
||||
return pydevd_base_schema.from_json(json_msg, update_ids_from_dap, on_dict_loaded=self._on_dict_loaded)
|
||||
|
||||
def add_dap_messages_listener(self, dap_commands_listener):
|
||||
self._dap_messages_listeners.append(dap_commands_listener)
|
||||
|
||||
def _on_dict_loaded(self, dct):
|
||||
for listener in self._dap_messages_listeners:
|
||||
for listener in self.py_db.dap_messages_listeners:
|
||||
listener.after_receive(dct)
|
||||
|
||||
@overrides(PyDBDaemonThread.do_kill_pydev_thread)
|
||||
|
|
@ -292,7 +287,7 @@ class ReaderThread(PyDBDaemonThread):
|
|||
return # Finished communication.
|
||||
|
||||
# We just received a json message, let's process it.
|
||||
self.process_net_command_json(self.global_debugger_holder.global_dbg, json_contents)
|
||||
self.process_net_command_json(self.py_db, json_contents)
|
||||
|
||||
continue
|
||||
else:
|
||||
|
|
@ -347,9 +342,10 @@ class ReaderThread(PyDBDaemonThread):
|
|||
class WriterThread(PyDBDaemonThread):
|
||||
''' writer thread writes out the commands in an infinite loop '''
|
||||
|
||||
def __init__(self, sock, terminate_on_socket_close=True):
|
||||
def __init__(self, sock, py_db, terminate_on_socket_close=True):
|
||||
PyDBDaemonThread.__init__(self)
|
||||
self.sock = sock
|
||||
self.py_db = py_db
|
||||
self._terminate_on_socket_close = terminate_on_socket_close
|
||||
self.setName("pydevd.Writer")
|
||||
self.cmdQueue = _queue.Queue()
|
||||
|
|
@ -358,11 +354,6 @@ class WriterThread(PyDBDaemonThread):
|
|||
else:
|
||||
self.timeout = 0.1
|
||||
|
||||
self._dap_messages_listeners = []
|
||||
|
||||
def add_dap_messages_listener(self, dap_commands_listener):
|
||||
self._dap_messages_listeners.append(dap_commands_listener)
|
||||
|
||||
def add_command(self, cmd):
|
||||
''' cmd is NetCommand '''
|
||||
if not self.killReceived: # we don't take new data after everybody die
|
||||
|
|
@ -401,7 +392,7 @@ class WriterThread(PyDBDaemonThread):
|
|||
return
|
||||
|
||||
if cmd.as_dict is not None:
|
||||
for listener in self._dap_messages_listeners:
|
||||
for listener in self.py_db.dap_messages_listeners:
|
||||
listener.before_send(cmd.as_dict)
|
||||
|
||||
cmd.send(self.sock)
|
||||
|
|
@ -413,7 +404,7 @@ class WriterThread(PyDBDaemonThread):
|
|||
time.sleep(self.timeout)
|
||||
except Exception:
|
||||
if self._terminate_on_socket_close:
|
||||
GlobalDebuggerHolder.global_dbg.finish_debugging_session()
|
||||
self.py_db.finish_debugging_session()
|
||||
if DebugInfoHolder.DEBUG_TRACE_LEVEL > 0:
|
||||
pydev_log_exception()
|
||||
|
||||
|
|
|
|||
|
|
@ -586,6 +586,9 @@ class PyDB(object):
|
|||
self._apply_filter_cache = {}
|
||||
self._ignore_system_exit_codes = set()
|
||||
|
||||
# DAP related
|
||||
self._dap_messages_listeners = []
|
||||
|
||||
if set_as_global:
|
||||
# Set as the global instance only after it's initialized.
|
||||
set_global_debugger(self)
|
||||
|
|
@ -1120,8 +1123,8 @@ class PyDB(object):
|
|||
if curr_writer:
|
||||
curr_writer.do_kill_pydev_thread()
|
||||
|
||||
self.writer = WriterThread(sock, terminate_on_socket_close=terminate_on_socket_close)
|
||||
self.reader = ReaderThread(sock, terminate_on_socket_close=terminate_on_socket_close)
|
||||
self.writer = WriterThread(sock, self, terminate_on_socket_close=terminate_on_socket_close)
|
||||
self.reader = ReaderThread(sock, self, terminate_on_socket_close=terminate_on_socket_close)
|
||||
self.writer.start()
|
||||
self.reader.start()
|
||||
|
||||
|
|
@ -1149,6 +1152,13 @@ class PyDB(object):
|
|||
def wait_for_server_socket_ready(self):
|
||||
self._server_socket_ready_event.wait()
|
||||
|
||||
@property
|
||||
def dap_messages_listeners(self):
|
||||
return self._dap_messages_listeners
|
||||
|
||||
def add_dap_messages_listener(self, listener):
|
||||
self._dap_messages_listeners.append(listener)
|
||||
|
||||
class _WaitForConnectionThread(PyDBDaemonThread):
|
||||
|
||||
def __init__(self, py_db):
|
||||
|
|
@ -2102,16 +2112,7 @@ def add_dap_messages_listener(dap_messages_listener):
|
|||
if py_db is None:
|
||||
raise AssertionError('PyDB is still not setup.')
|
||||
|
||||
writer = py_db.writer
|
||||
if writer is None:
|
||||
raise AssertionError('PyDB.writer is still not setup.')
|
||||
|
||||
reader = py_db.reader
|
||||
if reader is None:
|
||||
raise AssertionError('PyDB.reader is still not setup.')
|
||||
|
||||
writer.add_dap_messages_listener(dap_messages_listener)
|
||||
reader.add_dap_messages_listener(dap_messages_listener)
|
||||
py_db.add_dap_messages_listener(dap_messages_listener)
|
||||
|
||||
|
||||
def set_debug(setup):
|
||||
|
|
@ -2423,7 +2424,7 @@ def _locked_settrace(
|
|||
debugger.connect(host, port) # Note: connect can raise error.
|
||||
else:
|
||||
# Create a dummy writer and wait for the real connection.
|
||||
debugger.writer = WriterThread(NULL, terminate_on_socket_close=False)
|
||||
debugger.writer = WriterThread(NULL, debugger, terminate_on_socket_close=False)
|
||||
debugger.create_wait_for_connection_thread()
|
||||
|
||||
if dont_trace_start_patterns or dont_trace_end_paterns:
|
||||
|
|
|
|||
|
|
@ -33,13 +33,19 @@ def main(args):
|
|||
log.describe_environment("ptvsd.adapter startup environment:")
|
||||
|
||||
session = session.Session()
|
||||
if args.debug_server is None:
|
||||
if args.port is None:
|
||||
session.connect_to_ide()
|
||||
else:
|
||||
# If in debugServer mode, log everything to stderr.
|
||||
log.stderr_levels |= set(log.LEVELS)
|
||||
with session.accept_connection_from_ide(("localhost", args.debug_server)):
|
||||
pass
|
||||
|
||||
if args.for_server_on_port is not None:
|
||||
session.connect_to_server(("127.0.0.1", args.for_server_on_port))
|
||||
with session.accept_connection_from_ide((args.host, args.port)) as (_, port):
|
||||
try:
|
||||
session.server.set_debugger_property({"adapterPort": port})
|
||||
except AttributeError:
|
||||
pass
|
||||
session.wait_for_completion()
|
||||
|
||||
|
||||
|
|
@ -47,16 +53,29 @@ def _parse_argv(argv):
|
|||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--debug-server",
|
||||
"--port",
|
||||
type=int,
|
||||
nargs="?",
|
||||
default=None,
|
||||
const=8765,
|
||||
metavar="PORT",
|
||||
help="start the adapter in debugServer mode on the specified port",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--host",
|
||||
type=str,
|
||||
default="127.0.0.1",
|
||||
metavar="HOST",
|
||||
help="start the adapter in debugServer mode on the specified host",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--for-server-on-port",
|
||||
type=int,
|
||||
default=None,
|
||||
metavar="PORT",
|
||||
help=argparse.SUPPRESS
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--cls", action="store_true", help="clear screen before starting the debuggee"
|
||||
)
|
||||
|
|
@ -73,8 +92,8 @@ def _parse_argv(argv):
|
|||
)
|
||||
|
||||
args = parser.parse_args(argv[1:])
|
||||
if args.debug_server is None and args.log_stderr:
|
||||
parser.error("--log-stderr can only be used with --debug-server")
|
||||
if args.port is None and args.log_stderr:
|
||||
parser.error("--log-stderr can only be used with --port")
|
||||
return args
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ class IDE(components.Component):
|
|||
assert request.is_request("launch", "attach")
|
||||
if self._initialize_request is None:
|
||||
raise request.isnt_valid("Session is not initialized yet")
|
||||
if self.launcher or self.server:
|
||||
if self.launcher:
|
||||
raise request.isnt_valid("Session is already started")
|
||||
|
||||
self.session.no_debug = request("noDebug", json.default(False))
|
||||
|
|
@ -232,8 +232,14 @@ class IDE(components.Component):
|
|||
if self.session.no_debug:
|
||||
raise request.isnt_valid('"noDebug" is not supported for "attach"')
|
||||
|
||||
|
||||
|
||||
pid = request("processId", int, optional=True)
|
||||
if pid == ():
|
||||
if self.server is not None:
|
||||
# we are already connected to the debug server
|
||||
return
|
||||
|
||||
host = request("host", "127.0.0.1")
|
||||
port = request("port", int)
|
||||
if request("listen", False):
|
||||
|
|
@ -242,6 +248,9 @@ class IDE(components.Component):
|
|||
else:
|
||||
self.session.connect_to_server((host, port))
|
||||
else:
|
||||
if self.server is not None:
|
||||
raise request.isnt_valid("Session is already started")
|
||||
|
||||
ptvsd_args = request("ptvsdArgs", json.array(unicode))
|
||||
self.session.inject_server(pid, ptvsd_args)
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,10 @@ class Server(components.Component):
|
|||
request.wait_for_response()
|
||||
self.capabilities = self.Capabilities(self, request.response)
|
||||
|
||||
def set_debugger_property(self, arguments):
|
||||
assert isinstance(arguments, dict)
|
||||
self.channel.request("setDebuggerProperty", arguments=arguments)
|
||||
|
||||
# Generic request handler, used if there's no specific handler below.
|
||||
@message_handler
|
||||
def request(self, request):
|
||||
|
|
|
|||
|
|
@ -13,10 +13,14 @@ import threading
|
|||
import ptvsd
|
||||
from ptvsd.common import log, options as common_opts
|
||||
from ptvsd.server import options as server_opts
|
||||
from ptvsd.common.compat import queue
|
||||
from _pydevd_bundle.pydevd_constants import get_global_debugger
|
||||
from pydevd_file_utils import get_abs_path_real_path_and_base_from_file
|
||||
|
||||
|
||||
_QUEUE_TIMEOUT = 10
|
||||
|
||||
|
||||
def wait_for_attach():
|
||||
log.info("wait_for_attach()")
|
||||
dbg = get_global_debugger()
|
||||
|
|
@ -71,12 +75,61 @@ def _starts_debugging(func):
|
|||
|
||||
@_starts_debugging
|
||||
def enable_attach(dont_trace_start_patterns, dont_trace_end_patterns):
|
||||
server_opts.host, server_opts.port = pydevd._enable_attach(
|
||||
(server_opts.host, server_opts.port),
|
||||
if hasattr(enable_attach, "called"):
|
||||
raise RuntimeError("'enable_attach' can only be called once per process.")
|
||||
|
||||
host, port = pydevd._enable_attach(
|
||||
("127.0.0.1", 0),
|
||||
dont_trace_start_patterns=dont_trace_start_patterns,
|
||||
dont_trace_end_paterns=dont_trace_end_patterns,
|
||||
patch_multiprocessing=server_opts.multiprocess,
|
||||
)
|
||||
|
||||
log.info("pydevd debug server running at: {0}:{1}", host, port)
|
||||
|
||||
port_queue = queue.Queue()
|
||||
class _DAPMessagesListener(pydevd.IDAPMessagesListener):
|
||||
def before_send(self, msg):
|
||||
pass
|
||||
|
||||
def after_receive(self, msg):
|
||||
try:
|
||||
if msg["command"] == "setDebuggerProperty":
|
||||
port_queue.put(msg["arguments"]["adapterPort"])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
pydevd.add_dap_messages_listener(_DAPMessagesListener())
|
||||
|
||||
with pydevd.skip_subprocess_arg_patch():
|
||||
import subprocess
|
||||
adapter_args = [
|
||||
sys.executable,
|
||||
os.path.join(os.path.dirname(ptvsd.__file__), "adapter"),
|
||||
"--host",
|
||||
server_opts.host,
|
||||
"--port",
|
||||
str(server_opts.port),
|
||||
"--for-server-on-port",
|
||||
str(port)
|
||||
]
|
||||
|
||||
if common_opts.log_dir is not None:
|
||||
adapter_args += ["--log-dir", common_opts.log_dir]
|
||||
|
||||
log.info(
|
||||
"enable_attach() spawning attach-to-PID debugger injector: {0!r}", adapter_args
|
||||
)
|
||||
|
||||
# Adapter life time is expected to be longer than this process,
|
||||
# so never wait on the adapter process
|
||||
# TODO: Add adapter PID to ignore list https://github.com/microsoft/ptvsd/issues/1786
|
||||
subprocess.Popen(adapter_args, bufsize=0)
|
||||
|
||||
server_opts.port = port_queue.get(True, _QUEUE_TIMEOUT)
|
||||
|
||||
enable_attach.called = True
|
||||
log.info("ptvsd debug server running at: {0}:{1}", server_opts.host, server_opts.port)
|
||||
return server_opts.host, server_opts.port
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue