Ejemplo n.º 1
0
def enable_attach(secret, address = ('0.0.0.0', 5678), certfile = None, keyfile = None, redirect_output = True):
    """Enables Python Tools for Visual Studio to attach to this process remotely to debug Python code.

    The secret parameter is used to validate the clients - only those clients providing the valid
    secret will be allowed to connect to this server. On client side, the secret is prepended to
    the Qualifier string, separated from the hostname by '@', e.g.: [email protected]:5678.
    If secret is None, there's no validation, and any client can connect freely.

    The address parameter specifies the interface and port on which the debugging server should listen
    for TCP connections. It is in the same format as used for regular sockets of the AF_INET family,
    i.e. a tuple of (hostname, port). On client side, the server is identified by the Qualifier string
    in the usual hostname:port format, e.g.: myhost.cloudapp.net:5678.

    The certfile parameter is used to enable SSL. If not specified, or if set to None, the connection
    between this program and the debugger will be unsecure, and can be intercepted on the wire.
    If specified, the meaning of this parameter is the same as for ssl.wrap_socket. 

    The keyfile parameter is used together with certfile when SSL is enabled. Its meaning is the same
    as for ssl.wrap_socket.

    The redirect_output parameter specifies whether any output (on both stdout and stderr) produced
    by this program should be sent to the debugger. 

    This function returns immediately after setting up the debugging server, and does not block program
    execution. If you need to block until debugger is attached, call ptvsd.wait_for_attach. The debugger
    can be detached and re-attached multiple times after enable_attach is called.

    Only the thread on which this function is called, and any threads that are created after it returns,
    will be visible in the debugger once it is attached. Any threads that are already running before
    this function is called will not be visible.
    """

    if not ssl and (certfile or keyfile):
        raise ValueError('could not import the ssl module - SSL is not supported on this version of Python')

    if sys.platform == 'cli':
        # Check that IronPython was launched with -X:Frames and -X:Tracing, since we can't register our trace
        # func on the thread that calls enable_attach otherwise
        import clr
        x_tracing = clr.GetCurrentRuntime().GetLanguageByExtension('py').Options.Tracing
        x_frames = clr.GetCurrentRuntime().GetLanguageByExtension('py').Options.Frames
        if not x_tracing or not x_frames:
            raise RuntimeError('IronPython must be started with -X:Tracing and -X:Frames options to support PTVS remote debugging.')

    if redirect_output:
        _vspd.enable_output_redirection()

    atexit.register(_vspd.detach_process_and_notify_debugger)

    server = socket.socket()
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(address)
    server.listen(1)
    def server_thread_func():
        while True:
            client = None
            raw_client = None
            try:
                client, addr = server.accept()
                if certfile:
                    client = ssl.wrap_socket(client, server_side = True, ssl_version = ssl.PROTOCOL_TLSv1, certfile = certfile, keyfile = keyfile)
                client.sendall(PTVSDBG)
                _vspd.write_int(client, PTVSDBG_VER)

                response = client.recv(7)
                if response != PTVSDBG:
                    continue
                dbg_ver = _vspd.read_int(client)
                if dbg_ver != PTVSDBG_VER:
                    continue

                client_secret = _vspd.read_string(client)
                if secret is None or secret == client_secret:
                    client.sendall(ACPT)
                else:
                    client.sendall(RJCT)
                    continue

                response = client.recv(4)

                if response == INFO:
                    try:
                        pid = os.getpid()
                    except AttributeError:
                        pid = 0
                    _vspd.write_int(client, pid)

                    exe = sys.executable or ''
                    _vspd.write_string(client, exe)

                    try:
                        username = getpass.getuser()
                    except AttributeError:
                        username = ''
                    _vspd.write_string(client, username)

                    try:
                        impl = platform.python_implementation()
                    except AttributeError:
                        try:
                            impl = sys.implementation.name
                        except AttributeError:
                            impl = 'Python'

                    major, minor, micro, release_level, serial = sys.version_info

                    os_and_arch = platform.system()
                    if os_and_arch == "":
                        os_and_arch = sys.platform
                    try:
                        if sys.maxsize > 2**32:
                            os_and_arch += ' 64-bit'
                        else:
                            os_and_arch += ' 32-bit'
                    except AttributeError:
                        pass

                    version = '%s %s.%s.%s (%s)' % (impl, major, minor, micro, os_and_arch)
                    _vspd.write_string(client, version)

                elif response == ATCH:
                    if _vspd.DETACHED:
                        client.send(ACPT)

                        major, minor, micro, release_level, serial = sys.version_info
                        _vspd.write_int(client, major)
                        _vspd.write_int(client, minor)
                        _vspd.write_int(client, micro)

                        _vspd.attach_process_from_socket(client, report = True)
                        _vspd.mark_all_threads_for_break(_vspd.STEPPING_ATTACH_BREAK)
                        _attached.set()
                        client = None
                    else:
                        client.send(RJCT)

                elif response == REPL:
                    if not _vspd.DETACHED:
                        client.send(ACPT)
                        _vspd.connect_repl_using_socket(client)
                        client = None
                    else:
                        client.send(RJCT)

            except (socket.error, OSError):
                pass
            finally:
                if client is not None:
                    client.close()

    server_thread = threading.Thread(target = server_thread_func)
    server_thread.daemon = True
    server_thread.start()

    frames = []
    f = sys._getframe()
    while True:
        f = f.f_back
        if f is None:
            break
        frames.append(f)
    frames.reverse()
    cur_thread = _vspd.new_thread()
    for f in frames:
        cur_thread.push_frame(f)
    def replace_trace_func():
        for f in frames:
            f.f_trace = cur_thread.trace_func
    replace_trace_func()
    sys.settrace(cur_thread.trace_func)
    _vspd.intercept_threads(for_attach = True)
Ejemplo n.º 2
0
def enable_attach(secret, address = ('0.0.0.0', DEFAULT_PORT), certfile = None, keyfile = None, redirect_output = True):
    """Enables Python Tools for Visual Studio to attach to this process remotely
    to debug Python code.

    Parameters
    ----------
    secret : str
        Used to validate the clients - only those clients providing the valid
        secret will be allowed to connect to this server. On client side, the
        secret is prepended to the Qualifier string, separated from the
        hostname by ``'@'``, e.g.: ``'[email protected]:5678'``. If
        secret is ``None``, there's no validation, and any client can connect
        freely.
    address : (str, int), optional 
        Specifies the interface and port on which the debugging server should
        listen 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)``. On client side, the server is identified by the
        Qualifier string in the usual ``'hostname:port'`` format, e.g.:
        ``'myhost.cloudapp.net:5678'``. Default is ``('0.0.0.0', 5678)``.
    certfile : str, optional
        Used to enable SSL. If not specified, or if set to ``None``, the
        connection between this program and the debugger will be unsecure,
        and can be intercepted on the wire. If specified, the meaning of this
        parameter is the same as for `ssl.wrap_socket`. 
    keyfile : str, optional
        Used together with `certfile` when SSL is enabled. Its meaning is the
        same as for ``ssl.wrap_socket``.
    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``.

    Notes
    -----
    This function returns immediately after setting up the debugging server,
    and does not block program execution. If you need to block until debugger
    is attached, call `ptvsd.wait_for_attach`. The debugger can be detached
    and re-attached multiple times after `enable_attach` is called.

    This function can only be called once during the lifetime of the process. 
    On a second call, `AttachAlreadyEnabledError` is raised. In circumstances
    where the caller does not control how many times the function will be
    called (e.g. when a script with a single call is run more than once by
    a hosting app or framework), the call should be wrapped in ``try..except``.

    Only the thread on which this function is called, and any threads that are
    created after it returns, will be visible in the debugger once it is
    attached. Any threads that are already running before this function is
    called will not be visible.
    """

    if not ssl and (certfile or keyfile):
        raise ValueError('could not import the ssl module - SSL is not supported on this version of Python')

    if sys.platform == 'cli':
        # Check that IronPython was launched with -X:Frames and -X:Tracing, since we can't register our trace
        # func on the thread that calls enable_attach otherwise
        import clr
        x_tracing = clr.GetCurrentRuntime().GetLanguageByExtension('py').Options.Tracing
        x_frames = clr.GetCurrentRuntime().GetLanguageByExtension('py').Options.Frames
        if not x_tracing or not x_frames:
            raise RuntimeError('IronPython must be started with -X:Tracing and -X:Frames options to support PTVS remote debugging.')

    global _attach_enabled
    if _attach_enabled:
        raise AttachAlreadyEnabledError('ptvsd.enable_attach() has already been called in this process.')
    _attach_enabled = True

    if redirect_output:
        vspd.enable_output_redirection()

    atexit.register(vspd.detach_process_and_notify_debugger)

    server = socket.socket()
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(address)
    server.listen(1)
    def server_thread_func():
        while True:
            client = None
            raw_client = None
            try:
                client, addr = server.accept()
                if certfile:
                    client = ssl.wrap_socket(client, server_side = True, ssl_version = ssl.PROTOCOL_TLSv1, certfile = certfile, keyfile = keyfile)
                write_bytes(client, PTVSDBG)
                write_int(client, PTVSDBG_VER)

                response = read_bytes(client, 7)
                if response != PTVSDBG:
                    continue
                dbg_ver = read_int(client)
                if dbg_ver != PTVSDBG_VER:
                    continue

                client_secret = read_string(client)
                if secret is None or secret == client_secret:
                    write_bytes(client, ACPT)
                else:
                    write_bytes(client, RJCT)
                    continue

                response = read_bytes(client, 4)

                if response == INFO:
                    try:
                        pid = os.getpid()
                    except AttributeError:
                        pid = 0
                    write_int(client, pid)

                    exe = sys.executable or ''
                    write_string(client, exe)

                    try:
                        username = getpass.getuser()
                    except AttributeError:
                        username = ''
                    write_string(client, username)

                    try:
                        impl = platform.python_implementation()
                    except AttributeError:
                        try:
                            impl = sys.implementation.name
                        except AttributeError:
                            impl = 'Python'

                    major, minor, micro, release_level, serial = sys.version_info

                    os_and_arch = platform.system()
                    if os_and_arch == "":
                        os_and_arch = sys.platform
                    try:
                        if sys.maxsize > 2**32:
                            os_and_arch += ' 64-bit'
                        else:
                            os_and_arch += ' 32-bit'
                    except AttributeError:
                        pass

                    version = '%s %s.%s.%s (%s)' % (impl, major, minor, micro, os_and_arch)
                    write_string(client, version)

                    # Don't just drop the connection - let the debugger close it after it finishes reading.
                    client.recv(1)

                elif response == ATCH:
                    if vspd.DETACHED:
                        write_bytes(client, ACPT)
                        try:
                            pid = os.getpid()
                        except AttributeError:
                            pid = 0
                        write_int(client, pid)

                        major, minor, micro, release_level, serial = sys.version_info
                        write_int(client, major)
                        write_int(client, minor)
                        write_int(client, micro)

                        vspd.attach_process_from_socket(client, report = True)
                        vspd.mark_all_threads_for_break(vspd.STEPPING_ATTACH_BREAK)
                        _attached.set()
                        client = None
                    else:
                        write_bytes(client, RJCT)

                elif response == REPL:
                    if not vspd.DETACHED:
                        write_bytes(client, ACPT)
                        vspd.connect_repl_using_socket(client)
                        client = None
                    else:
                        write_bytes(client, RJCT)

            except (socket.error, OSError):
                pass
            finally:
                if client is not None:
                    client.close()

    server_thread = threading.Thread(target = server_thread_func)
    server_thread.daemon = True
    server_thread.start()

    frames = []
    f = sys._getframe()
    while True:
        f = f.f_back
        if f is None:
            break
        frames.append(f)
    frames.reverse()
    cur_thread = vspd.new_thread()
    for f in frames:
        cur_thread.push_frame(f)
    def replace_trace_func():
        for f in frames:
            f.f_trace = cur_thread.trace_func
    replace_trace_func()
    sys.settrace(cur_thread.trace_func)
    vspd.intercept_threads(for_attach = True)
Ejemplo n.º 3
0
def enable_attach(secret,
                  address=('0.0.0.0', DEFAULT_PORT),
                  certfile=None,
                  keyfile=None,
                  redirect_output=True):
    """Enables Python Tools for Visual Studio to attach to this process remotely
    to debug Python code.

    Parameters
    ----------
    secret : str
        Used to validate the clients - only those clients providing the valid
        secret will be allowed to connect to this server. On client side, the
        secret is prepended to the Qualifier string, separated from the
        hostname by ``'@'``, e.g.: ``'[email protected]:5678'``. If
        secret is ``None``, there's no validation, and any client can connect
        freely.
    address : (str, int), optional 
        Specifies the interface and port on which the debugging server should
        listen 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)``. On client side, the server is identified by the
        Qualifier string in the usual ``'hostname:port'`` format, e.g.:
        ``'myhost.cloudapp.net:5678'``. Default is ``('0.0.0.0', 5678)``.
    certfile : str, optional
        Used to enable SSL. If not specified, or if set to ``None``, the
        connection between this program and the debugger will be unsecure,
        and can be intercepted on the wire. If specified, the meaning of this
        parameter is the same as for `ssl.wrap_socket`. 
    keyfile : str, optional
        Used together with `certfile` when SSL is enabled. Its meaning is the
        same as for ``ssl.wrap_socket``.
    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``.

    Notes
    -----
    This function returns immediately after setting up the debugging server,
    and does not block program execution. If you need to block until debugger
    is attached, call `ptvsd.wait_for_attach`. The debugger can be detached
    and re-attached multiple times after `enable_attach` is called.

    This function can only be called once during the lifetime of the process. 
    On a second call, `AttachAlreadyEnabledError` is raised. In circumstances
    where the caller does not control how many times the function will be
    called (e.g. when a script with a single call is run more than once by
    a hosting app or framework), the call should be wrapped in ``try..except``.

    Only the thread on which this function is called, and any threads that are
    created after it returns, will be visible in the debugger once it is
    attached. Any threads that are already running before this function is
    called will not be visible.
    """

    if not ssl and (certfile or keyfile):
        raise ValueError(
            'could not import the ssl module - SSL is not supported on this version of Python'
        )

    if sys.platform == 'cli':
        # Check that IronPython was launched with -X:Frames and -X:Tracing, since we can't register our trace
        # func on the thread that calls enable_attach otherwise
        import clr
        x_tracing = clr.GetCurrentRuntime().GetLanguageByExtension(
            'py').Options.Tracing
        x_frames = clr.GetCurrentRuntime().GetLanguageByExtension(
            'py').Options.Frames
        if not x_tracing or not x_frames:
            raise RuntimeError(
                'IronPython must be started with -X:Tracing and -X:Frames options to support PTVS remote debugging.'
            )

    global _attach_enabled
    if _attach_enabled:
        raise AttachAlreadyEnabledError(
            'ptvsd.enable_attach() has already been called in this process.')
    _attach_enabled = True

    if redirect_output:
        vspd.enable_output_redirection()

    atexit.register(vspd.detach_process_and_notify_debugger)

    server = socket.socket()
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(address)
    server.listen(1)

    def server_thread_func():
        while True:
            client = None
            raw_client = None
            try:
                client, addr = server.accept()
                if certfile:
                    client = ssl.wrap_socket(client,
                                             server_side=True,
                                             ssl_version=ssl.PROTOCOL_TLSv1,
                                             certfile=certfile,
                                             keyfile=keyfile)
                write_bytes(client, PTVSDBG)
                write_int(client, PTVSDBG_VER)

                response = read_bytes(client, 7)
                if response != PTVSDBG:
                    continue
                dbg_ver = read_int(client)
                if dbg_ver != PTVSDBG_VER:
                    continue

                client_secret = read_string(client)
                if secret is None or secret == client_secret:
                    write_bytes(client, ACPT)
                else:
                    write_bytes(client, RJCT)
                    continue

                response = read_bytes(client, 4)

                if response == INFO:
                    try:
                        pid = os.getpid()
                    except AttributeError:
                        pid = 0
                    write_int(client, pid)

                    exe = sys.executable or ''
                    write_string(client, exe)

                    try:
                        username = getpass.getuser()
                    except AttributeError:
                        username = ''
                    write_string(client, username)

                    try:
                        impl = platform.python_implementation()
                    except AttributeError:
                        try:
                            impl = sys.implementation.name
                        except AttributeError:
                            impl = 'Python'

                    major, minor, micro, release_level, serial = sys.version_info

                    os_and_arch = platform.system()
                    if os_and_arch == "":
                        os_and_arch = sys.platform
                    try:
                        if sys.maxsize > 2**32:
                            os_and_arch += ' 64-bit'
                        else:
                            os_and_arch += ' 32-bit'
                    except AttributeError:
                        pass

                    version = '%s %s.%s.%s (%s)' % (impl, major, minor, micro,
                                                    os_and_arch)
                    write_string(client, version)

                    # Don't just drop the connection - let the debugger close it after it finishes reading.
                    client.recv(1)

                elif response == ATCH:
                    if vspd.DETACHED:
                        write_bytes(client, ACPT)
                        try:
                            pid = os.getpid()
                        except AttributeError:
                            pid = 0
                        write_int(client, pid)

                        major, minor, micro, release_level, serial = sys.version_info
                        write_int(client, major)
                        write_int(client, minor)
                        write_int(client, micro)

                        vspd.attach_process_from_socket(client, report=True)
                        vspd.mark_all_threads_for_break(
                            vspd.STEPPING_ATTACH_BREAK)
                        _attached.set()
                        client = None
                    else:
                        write_bytes(client, RJCT)

                elif response == REPL:
                    if not vspd.DETACHED:
                        write_bytes(client, ACPT)
                        vspd.connect_repl_using_socket(client)
                        client = None
                    else:
                        write_bytes(client, RJCT)

            except (socket.error, OSError):
                pass
            finally:
                if client is not None:
                    client.close()

    server_thread = threading.Thread(target=server_thread_func)
    server_thread.daemon = True
    server_thread.start()

    frames = []
    f = sys._getframe()
    while True:
        f = f.f_back
        if f is None:
            break
        frames.append(f)
    frames.reverse()
    cur_thread = vspd.new_thread()
    for f in frames:
        cur_thread.push_frame(f)

    def replace_trace_func():
        for f in frames:
            f.f_trace = cur_thread.trace_func

    replace_trace_func()
    sys.settrace(cur_thread.trace_func)
    vspd.intercept_threads(for_attach=True)