Exemplo n.º 1
0
def spawn_debuggee(
    session,
    start_request,
    python,
    launcher_path,
    adapter_host,
    args,
    cwd,
    console,
    console_title,
    sudo,
):
    # -E tells sudo to propagate environment variables to the target process - this
    # is necessary for launcher to get DEBUGPY_LAUNCHER_PORT and DEBUGPY_LOG_DIR.
    cmdline = ["sudo", "-E"] if sudo else []
    cmdline += python
    cmdline += [launcher_path]
    env = {}

    arguments = dict(start_request.arguments)
    if not session.no_debug:
        _, arguments["port"] = servers.listener.getsockname()
        arguments["adapterAccessToken"] = adapter.access_token

    def on_launcher_connected(sock):
        listener.close()
        stream = messaging.JsonIOStream.from_socket(sock)
        Launcher(session, stream)

    try:
        listener = sockets.serve("Launcher",
                                 on_launcher_connected,
                                 adapter_host,
                                 backlog=1)
    except Exception as exc:
        raise start_request.cant_handle(
            "{0} couldn't create listener socket for launcher: {1}", session,
            exc)

    try:
        launcher_host, launcher_port = listener.getsockname()
        launcher_addr = (launcher_port if launcher_host == "127.0.0.1" else
                         fmt("{0}:{1}", launcher_host, launcher_port))
        cmdline += [str(launcher_addr), "--"]
        cmdline += args

        if log.log_dir is not None:
            env[str("DEBUGPY_LOG_DIR")] = compat.filename_str(log.log_dir)
        if log.stderr.levels != {"warning", "error"}:
            env[str("DEBUGPY_LOG_STDERR")] = str(" ".join(log.stderr.levels))

        if console == "internalConsole":
            log.info("{0} spawning launcher: {1!r}", session, cmdline)
            try:
                for i, arg in enumerate(cmdline):
                    try:
                        cmdline[i] = compat.filename_str(arg)
                    except UnicodeEncodeError as exc:
                        raise start_request.cant_handle(
                            "Invalid command line argument {0!j}: {1}", arg,
                            exc)

                # If we are talking to the client over stdio, sys.stdin and sys.stdout
                # are redirected to avoid mangling the DAP message stream. Make sure
                # the launcher also respects that.
                subprocess.Popen(
                    cmdline,
                    cwd=cwd,
                    env=dict(list(os.environ.items()) + list(env.items())),
                    stdin=sys.stdin,
                    stdout=sys.stdout,
                    stderr=sys.stderr,
                )
            except Exception as exc:
                raise start_request.cant_handle(
                    "Failed to spawn launcher: {0}", exc)
        else:
            log.info('{0} spawning launcher via "runInTerminal" request.',
                     session)
            session.client.capabilities.require("supportsRunInTerminalRequest")
            kinds = {
                "integratedTerminal": "integrated",
                "externalTerminal": "external"
            }
            request_args = {
                "kind": kinds[console],
                "title": console_title,
                "args": cmdline,
                "env": env,
            }
            if cwd is not None:
                request_args["cwd"] = cwd
            try:
                session.client.channel.request("runInTerminal", request_args)
            except messaging.MessageHandlingError as exc:
                exc.propagate(start_request)

        # If using sudo, it might prompt for password, and launcher won't start running
        # until the user enters it, so don't apply timeout in that case.
        if not session.wait_for(
                lambda: session.launcher,
                timeout=(None if sudo else common.PROCESS_SPAWN_TIMEOUT),
        ):
            raise start_request.cant_handle(
                "Timed out waiting for launcher to connect")

        try:
            session.launcher.channel.request(start_request.command, arguments)
        except messaging.MessageHandlingError as exc:
            exc.propagate(start_request)

        if session.no_debug:
            return

        if not session.wait_for(
                lambda: session.launcher.pid is not None,
                timeout=common.PROCESS_SPAWN_TIMEOUT,
        ):
            raise start_request.cant_handle(
                'Timed out waiting for "process" event from launcher')

        # Wait for the first incoming connection regardless of the PID - it won't
        # necessarily match due to the use of stubs like py.exe or "conda run".
        conn = servers.wait_for_connection(
            session, lambda conn: True, timeout=common.PROCESS_SPAWN_TIMEOUT)
        if conn is None:
            raise start_request.cant_handle(
                "Timed out waiting for debuggee to spawn")
        conn.attach_to_session(session)

    finally:
        listener.close()
Exemplo n.º 2
0
    def attach_request(self, request):
        if self.session.no_debug:
            raise request.isnt_valid('"noDebug" is not supported for "attach"')

        host = request("host", unicode, optional=True)
        port = request("port", int, optional=True)
        listen = request("listen", dict, optional=True)
        connect = request("connect", dict, optional=True)
        pid = request("processId", (int, unicode), optional=True)
        sub_pid = request("subProcessId", int, optional=True)

        if host != () or port != ():
            if listen != ():
                raise request.isnt_valid(
                    '"listen" and "host"/"port" are mutually exclusive')
            if connect != ():
                raise request.isnt_valid(
                    '"connect" and "host"/"port" are mutually exclusive')
        if listen != ():
            if connect != ():
                raise request.isnt_valid(
                    '"listen" and "connect" are mutually exclusive')
            if pid != ():
                raise request.isnt_valid(
                    '"listen" and "processId" are mutually exclusive')
            if sub_pid != ():
                raise request.isnt_valid(
                    '"listen" and "subProcessId" are mutually exclusive')
        if pid != () and sub_pid != ():
            raise request.isnt_valid(
                '"processId" and "subProcessId" are mutually exclusive')

        if listen != ():
            host = listen("host", "127.0.0.1")
            port = listen("port", int)
            adapter.access_token = None
            host, port = servers.serve(host, port)
        else:
            host, port = servers.serve()

        # There are four distinct possibilities here.
        #
        # If "processId" is specified, this is attach-by-PID. We need to inject the
        # debug server into the designated process, and then wait until it connects
        # back to us. Since the injected server can crash, there must be a timeout.
        #
        # If "subProcessId" is specified, this is attach to a known subprocess, likely
        # in response to a "debugpyAttach" event. If so, the debug server should be
        # connected already, and thus the wait timeout is zero.
        #
        # If "listen" is specified, this is attach-by-socket with the server expected
        # to connect to the adapter via debugpy.connect(). There is no PID known in
        # advance, so just wait until the first server connection indefinitely, with
        # no timeout.
        #
        # If "connect" is specified, this is attach-by-socket in which the server has
        # spawned the adapter via debugpy.listen(). There is no PID known to the client
        # in advance, but the server connection should be either be there already, or
        # the server should be connecting shortly, so there must be a timeout.
        #
        # In the last two cases, if there's more than one server connection already,
        # this is a multiprocess re-attach. The client doesn't know the PID, so we just
        # connect it to the oldest server connection that we have - in most cases, it
        # will be the one for the root debuggee process, but if it has exited already,
        # it will be some subprocess.
        if pid != ():
            if not isinstance(pid, int):
                try:
                    pid = int(pid)
                except Exception:
                    raise request.isnt_valid(
                        '"processId" must be parseable as int')
            debugpy_args = request("debugpyArgs", json.array(unicode))
            servers.inject(pid, debugpy_args)
            timeout = common.PROCESS_SPAWN_TIMEOUT
            pred = lambda conn: conn.pid == pid
        else:
            if sub_pid == ():
                pred = lambda conn: True
                timeout = common.PROCESS_SPAWN_TIMEOUT if listen == (
                ) else None
            else:
                pred = lambda conn: conn.pid == sub_pid
                timeout = 0

        self.channel.send_event("debugpyWaitingForServer", {
            "host": host,
            "port": port
        })
        conn = servers.wait_for_connection(self.session, pred, timeout)
        if conn is None:
            if sub_pid != ():
                # If we can't find a matching subprocess, it's not always an error -
                # it might have already exited, or didn't even get a chance to connect.
                # To prevent the client from complaining, pretend that the "attach"
                # request was successful, but that the session terminated immediately.
                request.respond({})
                self.session.finalize(
                    fmt('No known subprocess with "subProcessId":{0}',
                        sub_pid))
                return

            raise request.cant_handle(
                ("Timed out waiting for debug server to connect." if timeout
                 else "There is no debug server connected to this adapter."),
                sub_pid,
            )

        try:
            conn.attach_to_session(self.session)
        except ValueError:
            request.cant_handle("{0} is already being debugged.", conn)
Exemplo n.º 3
0
def spawn_debuggee(session, start_request, args, console, console_title):
    cmdline = [sys.executable, os.path.dirname(launcher.__file__)] + args
    env = {}

    arguments = dict(start_request.arguments)
    if not session.no_debug:
        _, arguments["port"] = servers.listener.getsockname()
        arguments["adapterAccessToken"] = adapter.access_token

    def on_launcher_connected(sock):
        listener.close()
        stream = messaging.JsonIOStream.from_socket(sock)
        Launcher(session, stream)

    try:
        listener = sockets.serve(
            "Launcher", on_launcher_connected, "127.0.0.1", backlog=0
        )
    except Exception as exc:
        raise start_request.cant_handle(
            "{0} couldn't create listener socket for {1}: {2}",
            session,
            session.launcher,
            exc,
        )

    try:
        _, launcher_port = listener.getsockname()

        env[str("DEBUGPY_LAUNCHER_PORT")] = str(launcher_port)
        if log.log_dir is not None:
            env[str("DEBUGPY_LOG_DIR")] = compat.filename_str(log.log_dir)
        if log.stderr.levels != {"warning", "error"}:
            env[str("DEBUGPY_LOG_STDERR")] = str(" ".join(log.stderr.levels))

        if console == "internalConsole":
            log.info("{0} spawning launcher: {1!r}", session, cmdline)
            try:
                # If we are talking to the client over stdio, sys.stdin and sys.stdout
                # are redirected to avoid mangling the DAP message stream. Make sure
                # the launcher also respects that.
                subprocess.Popen(
                    cmdline,
                    env=dict(list(os.environ.items()) + list(env.items())),
                    stdin=sys.stdin,
                    stdout=sys.stdout,
                    stderr=sys.stderr,
                )
            except Exception as exc:
                raise start_request.cant_handle(
                    "{0} failed to spawn {1}: {2}", session, session.launcher, exc
                )
        else:
            log.info('{0} spawning launcher via "runInTerminal" request.', session)
            session.client.capabilities.require("supportsRunInTerminalRequest")
            kinds = {"integratedTerminal": "integrated", "externalTerminal": "external"}
            try:
                session.client.channel.request(
                    "runInTerminal",
                    {
                        "kind": kinds[console],
                        "title": console_title,
                        "args": cmdline,
                        "env": env,
                    },
                )
            except messaging.MessageHandlingError as exc:
                exc.propagate(start_request)

        if not session.wait_for(lambda: session.launcher, timeout=10):
            raise start_request.cant_handle(
                "{0} timed out waiting for {1} to connect", session, session.launcher
            )

        try:
            session.launcher.channel.request(start_request.command, arguments)
        except messaging.MessageHandlingError as exc:
            exc.propagate(start_request)

        if session.no_debug:
            return

        if not session.wait_for(lambda: session.launcher.pid is not None, timeout=10):
            raise start_request.cant_handle(
                '{0} timed out waiting for "process" event from {1}',
                session,
                session.launcher,
            )

        # Wait for the first incoming connection regardless of the PID - it won't
        # necessarily match due to the use of stubs like py.exe or "conda run".
        conn = servers.wait_for_connection(session, lambda conn: True, timeout=10)
        if conn is None:
            raise start_request.cant_handle(
                "{0} timed out waiting for debuggee to spawn", session
            )
        conn.attach_to_session(session)

    finally:
        listener.close()