Skip to content

Commit

Permalink
Fix microsoft#200: Local attach
Browse files Browse the repository at this point in the history
Add --pid option to ptvsd command line. When used, injects the debugger
into the process with the specified ID, such that it connects back to
the specified host/port.
  • Loading branch information
int19h committed Sep 10, 2018
1 parent f453ed1 commit 52dedb3
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 14 deletions.
39 changes: 26 additions & 13 deletions ptvsd/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import os.path
import sys

from ptvsd._attach import attach_main
from ptvsd._local import debug_main, run_main
from ptvsd.socket import Address
from ptvsd.version import __version__, __author__ # noqa
Expand Down Expand Up @@ -46,6 +47,7 @@
USAGE = """
{0} [-h] [-V] [--nodebug] [--host HOST | --server-host HOST] --port PORT -m MODULE [arg ...]
{0} [-h] [-V] [--nodebug] [--host HOST | --server-host HOST] --port PORT FILENAME [arg ...]
{0} [-h] [-V] --host HOST --port PORT --pid PROCESS_ID
""" # noqa


Expand Down Expand Up @@ -129,8 +131,8 @@ def _group_args(argv):
supported.append(arg)

# ptvsd support
elif arg in ('--host', '--server-host', '--port', '-m'):
if arg == '-m':
elif arg in ('--host', '--server-host', '--port', '--pid', '-m'):
if arg == '-m' or arg == '--pid':
gottarget = True
supported.append(arg)
if nextarg is not None:
Expand All @@ -155,14 +157,17 @@ def _parse_args(prog, argv):
prog=prog,
usage=USAGE.format(prog),
)

parser.add_argument('--nodebug', action='store_true')

host = parser.add_mutually_exclusive_group()
host.add_argument('--host')
host.add_argument('--server-host')
parser.add_argument('--port', type=int, required=True)

target = parser.add_mutually_exclusive_group(required=True)
target.add_argument('-m', dest='module')
target.add_argument('--pid', type=int)
target.add_argument('filename', nargs='?')

parser.add_argument('--single-session', action='store_true')
Expand All @@ -186,28 +191,36 @@ def _parse_args(prog, argv):
else:
args.address = Address.as_client(clienthost, ns.pop('port'))

pid = ns.pop('pid')
module = ns.pop('module')
filename = ns.pop('filename')
if module is None:
args.name = filename
args.kind = 'script'
else:
if pid is not None:
args.name = pid
args.kind = 'pid'
elif module is not None:
args.name = module
args.kind = 'module'
#if argv[-1] != args.name or (module and argv[-1] != '-m'):
# parser.error('script/module must be last arg')
else:
args.name = filename
args.kind = 'script'

return args


def main(addr, name, kind, extra=(), nodebug=False, **kwargs):
if nodebug:
def handle_args(addr, name, kind, extra=(), nodebug=False, **kwargs):
if kind == 'pid':
attach_main(addr, name, *extra, **kwargs)
elif nodebug:
run_main(addr, name, kind, *extra, **kwargs)
else:
debug_main(addr, name, kind, *extra, **kwargs)


if __name__ == '__main__':
args, extra = parse_args()
main(args.address, args.name, args.kind, extra, nodebug=args.nodebug,
def main(argv=None):
args, extra = parse_args(argv)
handle_args(args.address, args.name, args.kind, extra, nodebug=args.nodebug,
singlesession=args.single_session, wait=args.wait)


if __name__ == '__main__' or __name__ == 'ptvsd.__main__':
main()
39 changes: 39 additions & 0 deletions ptvsd/_attach.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE in the project root
# for license information.

import os.path
import sys

import ptvsd
import pydevd

def attach_main(address, pid, *extra, **kwargs):
hostname, port_num = address

ptvsd_path = os.path.join(ptvsd.__file__, '../..')
pydevd_attach_to_process_path = os.path.join(os.path.dirname(pydevd.__file__), 'pydevd_attach_to_process')
sys.path.append(pydevd_attach_to_process_path)
from add_code_to_python_process import run_python_code

# The code must not contain single quotes. Also, it might be running on a different
# Python version once it's injected. Encoding string literals as raw UTF-8 byte
# values takes care of both.
code = '''
ptvsd_path = bytearray({ptvsd_path}).decode("utf-8")
hostname = bytearray({hostname}).decode("utf-8")
import sys
sys.path.insert(0, ptvsd_path)
import ptvsd
del sys.path[0]
from ptvsd._remote import attach
attach((hostname, {port_num}))
'''

code = code.format(
ptvsd_path=repr(list(ptvsd_path.encode('utf-8'))),
hostname=repr(list(hostname.encode('utf-8'))),
port_num=port_num)
run_python_code(pid, code, connect_debugger_tracing=True)
18 changes: 18 additions & 0 deletions ptvsd/_remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,21 @@ def start_daemon():
stderrToServer=redirect_output,
port=port,
suspend=False)


def attach(address,
redirect_output=True,
_pydevd=pydevd,
_install=install,
**kwargs):

host, port = address
daemon = _install(_pydevd,
address,
singlesession=False,
**kwargs)
_pydevd.settrace(host=host,
port=port,
stdoutToServer=redirect_output,
stderrToServer=redirect_output,
suspend=False)
29 changes: 28 additions & 1 deletion ptvsd/attach_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
# for license information.

from ptvsd._remote import (
enable_attach as ptvsd_enable_attach, _pydevd_settrace,
attach as ptvsd_attach,
enable_attach as ptvsd_enable_attach,
_pydevd_settrace,
)
from ptvsd.wrapper import debugger_attached

Expand Down Expand Up @@ -71,6 +73,31 @@ def enable_attach(address=(DEFAULT_HOST, DEFAULT_PORT), redirect_output=True):
)


def attach(address, redirect_output=True):
"""Attaches this process to the debugger listening on a given address.
Parameters
----------
address : (str, int), optional
Specifies the interface and port on which the debugger is listening
for TCP connections. It is in the same format as used for
regular sockets of the `socket.AF_INET` family, i.e. a tuple of
``(hostname, port)``.
redirect_output : bool, optional
Specifies whether any output (on both `stdout` and `stderr`) produced
by this program should be sent to the debugger. Default is ``True``.
"""
if is_attached():
return
debugger_attached.clear()

# Ensure port is int
port = address[1]
address = (address[0], port if type(port) is int else int(port))

ptvsd_attach(address, redirect_output=redirect_output)


# TODO: Add disable_attach()?


Expand Down

0 comments on commit 52dedb3

Please sign in to comment.