Add parent-session-pid argument

Add the ability to specify the parent process id when connecting a new
DAP server to the client. This value is used instead of the actual
process' parent id so that it can be associated with a specific debug
session even if it hasn't been spawned directly by that parent process.
This commit is contained in:
Jordan Borean 2025-07-04 09:05:24 +10:00
parent 0d65353cc6
commit ddb7c500f9
No known key found for this signature in database
GPG key ID: 2AAC89085FBBDAB5
4 changed files with 22 additions and 4 deletions

View file

@ -2947,6 +2947,7 @@ def settrace(
client_access_token=None,
notify_stdin=True,
protocol=None,
ppid=0,
**kwargs,
):
"""Sets the tracing function with the pydev debug function and initializes needed facilities.
@ -3006,6 +3007,11 @@ def settrace(
When using in Eclipse the protocol should not be passed, but when used in VSCode
or some other IDE/editor that accepts the Debug Adapter Protocol then 'dap' should
be passed.
:param ppid:
Override the parent process id (PPID) for the current debugging session. This PPID is
reported to the debug client (IDE) and can be used to act like a child process of an
existing debugged process without being a child process.
"""
if protocol and protocol.lower() == "dap":
pydevd_defaults.PydevdCustomization.DEFAULT_PROTOCOL = pydevd_constants.HTTP_JSON_PROTOCOL
@ -3034,6 +3040,7 @@ def settrace(
client_access_token,
__setup_holder__=__setup_holder__,
notify_stdin=notify_stdin,
ppid=ppid,
)
@ -3057,6 +3064,7 @@ def _locked_settrace(
client_access_token,
__setup_holder__,
notify_stdin,
ppid,
):
if patch_multiprocessing:
try:
@ -3088,6 +3096,7 @@ def _locked_settrace(
"port": int(port),
"multiprocess": patch_multiprocessing,
"skip-notify-stdin": not notify_stdin,
pydevd_constants.ARGUMENT_PPID: ppid,
}
SetupHolder.setup = setup

View file

@ -120,7 +120,7 @@ def listen(
...
@_api()
def connect(__endpoint: Endpoint | int, *, access_token: str | None = None) -> Endpoint:
def connect(__endpoint: Endpoint | int, *, access_token: str | None = None, parent_session_pid: int | None = None) -> Endpoint:
"""Tells an existing debug adapter instance that is listening on the
specified address to debug this process.
@ -131,6 +131,10 @@ def connect(__endpoint: Endpoint | int, *, access_token: str | None = None) -> E
`access_token` must be the same value that was passed to the adapter
via the `--server-access-token` command-line switch.
`parent_session_pid` is the PID of the parent session to associate
with. This is useful if running in a process that is not an immediate
child of the parent process being debugged.
This function does't wait for a client to connect to the debug
adapter that it connects to. Use `wait_for_client` to block
execution until the client connects.

View file

@ -293,9 +293,9 @@ listen.called = False
@_starts_debugging
def connect(address, settrace_kwargs, access_token=None):
def connect(address, settrace_kwargs, access_token=None, parent_session_pid=None):
host, port = address
_settrace(host=host, port=port, client_access_token=access_token, **settrace_kwargs)
_settrace(host=host, port=port, client_access_token=access_token, ppid=parent_session_pid or 0, **settrace_kwargs)
class wait_for_client:

View file

@ -34,6 +34,7 @@ Usage: debugpy --listen | --connect
[--wait-for-client]
[--configure-<name> <value>]...
[--log-to <path>] [--log-to-stderr]
[--parent-session-pid <pid>]]
{1}
[<arg>]...
""".format(
@ -51,6 +52,7 @@ class Options(object):
wait_for_client = False
adapter_access_token = None
config: Dict[str, Any] = {}
parent_session_pid: Union[int, None] = None
options = Options()
@ -179,6 +181,7 @@ switches = [
("--connect", "<address>", set_address("connect")),
("--wait-for-client", None, set_const("wait_for_client", True)),
("--configure-.+", "<value>", set_config),
("--parent-session-pid", "<pid>", set_arg("parent_session_pid", lambda x: int(x) if x else None)),
# Switches that are used internally by the client or debugpy itself.
("--adapter-access-token", "<token>", set_arg("adapter_access_token")),
@ -230,6 +233,8 @@ def parse_args():
raise ValueError("either --listen or --connect is required")
if options.adapter_access_token is not None and options.mode != "connect":
raise ValueError("--adapter-access-token requires --connect")
if options.parent_session_pid is not None and options.mode != "connect":
raise ValueError("--parent-session-pid requires --connect")
if options.target_kind == "pid" and options.wait_for_client:
raise ValueError("--pid does not support --wait-for-client")
@ -321,7 +326,7 @@ def start_debugging(argv_0):
if options.mode == "listen" and options.address is not None:
debugpy.listen(options.address)
elif options.mode == "connect" and options.address is not None:
debugpy.connect(options.address, access_token=options.adapter_access_token)
debugpy.connect(options.address, access_token=options.adapter_access_token, parent_session_pid=options.parent_session_pid)
else:
raise AssertionError(repr(options.mode))