Exemple #1
0
    def __init__(self, sock):
        from ptvsd.adapter import sessions

        self.server = None
        """The Server component, if this debug server belongs to Session.
        """

        self.pid = None

        stream = messaging.JsonIOStream.from_socket(sock, str(self))
        self.channel = messaging.JsonMessageChannel(stream, self)
        self.channel.start()

        try:
            info = self.channel.request("pydevdSystemInfo")
            process_info = info("process", json.object())
            self.pid = process_info("pid", int)
            self.ppid = process_info("ppid", int, optional=True)
            if self.ppid == ():
                self.ppid = None

            self.channel.name = stream.name = str(self)
            with _lock:
                if any(conn.pid == self.pid for conn in _connections):
                    raise KeyError(
                        fmt("{0} is already connected to this adapter", self))
                _connections.append(self)
                _connections_changed.set()

        except Exception:
            log.exception("Failed to accept incoming server connection:")
            # If we couldn't retrieve all the necessary info from the debug server,
            # or there's a PID clash, we don't want to track this debuggee anymore,
            # but we want to continue accepting connections.
            self.channel.close()
            return

        parent_session = sessions.get(self.ppid)
        if parent_session is None:
            log.info("No active debug session for parent process of {0}.",
                     self)
        else:
            try:
                parent_session.ide.notify_of_subprocess(self)
            except Exception:
                # This might fail if the IDE concurrently disconnects from the parent
                # session. We still want to keep the connection around, in case the
                # IDE reconnects later. If the parent session was "launch", it'll take
                # care of closing the remaining server connections.
                log.exception("Failed to notify parent session about {0}:",
                              self)
Exemple #2
0
 def _process_request(self, request):
     self.timeline.record_request(request, block=False)
     if request.command == "runInTerminal":
         args = request("args", json.array(unicode))
         cwd = request("cwd", ".")
         env = request("env", json.object(unicode))
         try:
             exe = args.pop(0)
             assert not len(self.spawn_debuggee.env)
             self.spawn_debuggee.env = env
             self.spawn_debuggee(args, cwd, exe=exe)
             return {}
         except OSError as exc:
             log.exception('"runInTerminal" failed:')
             raise request.cant_handle(str(exc))
     else:
         raise request.isnt_valid("not supported")
Exemple #3
0
def launch_request(request):
    debug_options = set(request("debugOptions", json.array(unicode)))

    # Handling of properties that can also be specified as legacy "debugOptions" flags.
    # If property is explicitly set to false, but the flag is in "debugOptions", treat
    # it as an error. Returns None if the property wasn't explicitly set either way.
    def property_or_debug_option(prop_name, flag_name):
        assert prop_name[0].islower() and flag_name[0].isupper()

        value = request(prop_name, bool, optional=True)
        if value == ():
            value = None

        if flag_name in debug_options:
            if value is False:
                raise request.isnt_valid(
                    '{0!j}:false and "debugOptions":[{1!j}] are mutually exclusive',
                    prop_name,
                    flag_name,
                )
            value = True

        return value

    cmdline = []
    if property_or_debug_option("sudo", "Sudo"):
        if sys.platform == "win32":
            raise request.cant_handle('"sudo":true is not supported on Windows.')
        else:
            cmdline += ["sudo"]

    # "pythonPath" is a deprecated legacy spelling. If "python" is missing, then try
    # the alternative. But if both are missing, the error message should say "python".
    python_key = "python"
    if python_key in request:
        if "pythonPath" in request:
            raise request.isnt_valid(
                '"pythonPath" is not valid if "python" is specified'
            )
    elif "pythonPath" in request:
        python_key = "pythonPath"
    python = request(python_key, json.array(unicode, vectorize=True, size=(1,)))
    if not len(python):
        python = [compat.filename(sys.executable)]
    cmdline += python

    if not request("noDebug", json.default(False)):
        port = request("port", int)
        cmdline += [
            compat.filename(os.path.dirname(ptvsd.__file__)),
            "--client",
            "--host",
            "127.0.0.1",
            "--port",
            str(port),
        ]
        client_access_token = request("clientAccessToken", unicode, optional=True)
        if client_access_token != ():
            cmdline += ["--client-access-token", compat.filename(client_access_token)]
        ptvsd_args = request("ptvsdArgs", json.array(unicode))
        cmdline += ptvsd_args

    program = module = code = ()
    if "program" in request:
        program = request("program", json.array(unicode, vectorize=True, size=(1,)))
        cmdline += program
        process_name = program[0]
    if "module" in request:
        module = request("module", json.array(unicode, vectorize=True, size=(1,)))
        cmdline += ["-m"] + module
        process_name = module[0]
    if "code" in request:
        code = request("code", json.array(unicode, vectorize=True, size=(1,)))
        cmdline += ["-c"] + code
        process_name = python[0]

    num_targets = len([x for x in (program, module, code) if x != ()])
    if num_targets == 0:
        raise request.isnt_valid(
            'either "program", "module", or "code" must be specified'
        )
    elif num_targets != 1:
        raise request.isnt_valid(
            '"program", "module", and "code" are mutually exclusive'
        )

    cmdline += request("args", json.array(unicode))

    cwd = request("cwd", unicode, optional=True)
    if cwd == ():
        # If it's not specified, but we're launching a file rather than a module,
        # and the specified path has a directory in it, use that.
        cwd = None if program == () else (os.path.dirname(program[0]) or None)

    env = os.environ.copy()
    if "PTVSD_TEST" in env:
        # If we're running as part of a ptvsd test, make sure that codecov is not
        # applied to the debuggee, since it will conflict with pydevd.
        env.pop("COV_CORE_SOURCE", None)
    env.update(request("env", json.object(unicode)))

    if request("gevent", False):
        env["GEVENT_SUPPORT"] = "True"

    redirect_output = property_or_debug_option("redirectOutput", "RedirectOutput")
    if redirect_output is None:
        # If neither the property nor the option were specified explicitly, choose
        # the default depending on console type - "internalConsole" needs it to
        # provide any output at all, but it's unnecessary for the terminals.
        redirect_output = request("console", unicode) == "internalConsole"
    if redirect_output:
        # sys.stdout buffering must be disabled - otherwise we won't see the output
        # at all until the buffer fills up.
        env["PYTHONUNBUFFERED"] = "1"
        # Force UTF-8 output to minimize data loss due to re-encoding.
        env["PYTHONIOENCODING"] = "utf-8"

    if property_or_debug_option("waitOnNormalExit", "WaitOnNormalExit"):
        debuggee.wait_on_exit_predicates.append(lambda code: code == 0)
    if property_or_debug_option("waitOnAbnormalExit", "WaitOnAbnormalExit"):
        debuggee.wait_on_exit_predicates.append(lambda code: code != 0)

    if sys.version_info < (3,):
        # Popen() expects command line and environment to be bytes, not Unicode.
        # Assume that values are filenames - it's usually either that, or numbers -
        # but don't allow encoding to fail if we guessed wrong.
        encode = functools.partial(compat.filename_bytes, errors="replace")
        cmdline = [encode(s) for s in cmdline]
        env = {encode(k): encode(v) for k, v in env.items()}

    debuggee.spawn(process_name, cmdline, cwd, env, redirect_output)
    return {}
Exemple #4
0
    def __init__(self, sock):
        from ptvsd.adapter import sessions

        self.disconnected = False

        self.server = None
        """The Server component, if this debug server belongs to Session.
        """

        self.pid = None

        stream = messaging.JsonIOStream.from_socket(sock, str(self))
        self.channel = messaging.JsonMessageChannel(stream, self)
        self.channel.start()

        try:
            self.authenticate()
            info = self.channel.request("pydevdSystemInfo")
            process_info = info("process", json.object())
            self.pid = process_info("pid", int)
            self.ppid = process_info("ppid", int, optional=True)
            if self.ppid == ():
                self.ppid = None
            self.channel.name = stream.name = str(self)

            ptvsd_dir = os.path.dirname(os.path.dirname(ptvsd.__file__))
            # Note: we must check if 'ptvsd' is not already in sys.modules because the
            # evaluation of an import at the wrong time could deadlock Python due to
            # its import lock.
            #
            # So, in general this evaluation shouldn't do anything. It's only
            # important when pydevd attaches automatically to a subprocess. In this
            # case, we have to make sure that ptvsd is properly put back in the game
            # for users to be able to use it.v
            #
            # In this case (when the import is needed), this evaluation *must* be done
            # before the configurationDone request is sent -- if this is not respected
            # it's possible that pydevd already started secondary threads to handle
            # commands, in which case it's very likely that this command would be
            # evaluated at the wrong thread and the import could potentially deadlock
            # the program.
            #
            # Note 2: the sys module is guaranteed to be in the frame globals and
            # doesn't need to be imported.
            inject_ptvsd = """
if 'ptvsd' not in sys.modules:
    sys.path.insert(0, {ptvsd_dir!r})
    try:
        import ptvsd
    finally:
        del sys.path[0]
"""
            inject_ptvsd = fmt(inject_ptvsd, ptvsd_dir=ptvsd_dir)

            try:
                self.channel.request("evaluate", {"expression": inject_ptvsd})
            except messaging.MessageHandlingError:
                # Failure to inject is not a fatal error - such a subprocess can
                # still be debugged, it just won't support "import ptvsd" in user
                # code - so don't terminate the session.
                log.exception("Failed to inject ptvsd into {0}:",
                              self,
                              level="warning")

            with _lock:
                # The server can disconnect concurrently before we get here, e.g. if
                # it was force-killed. If the disconnect() handler has already run,
                # don't register this server or report it, since there's nothing to
                # deregister it.
                if self.disconnected:
                    return

                if any(conn.pid == self.pid for conn in _connections):
                    raise KeyError(
                        fmt("{0} is already connected to this adapter", self))
                _connections.append(self)
                _connections_changed.set()

        except Exception:
            log.exception("Failed to accept incoming server connection:")
            self.channel.close()

            # If this was the first server to connect, and the main thread is inside
            # wait_until_disconnected(), we want to unblock it and allow it to exit.
            dont_wait_for_first_connection()

            # If we couldn't retrieve all the necessary info from the debug server,
            # or there's a PID clash, we don't want to track this debuggee anymore,
            # but we want to continue accepting connections.
            return

        parent_session = sessions.get(self.ppid)
        if parent_session is None:
            log.info("No active debug session for parent process of {0}.",
                     self)
        else:
            try:
                parent_session.ide.notify_of_subprocess(self)
            except Exception:
                # This might fail if the IDE concurrently disconnects from the parent
                # session. We still want to keep the connection around, in case the
                # IDE reconnects later. If the parent session was "launch", it'll take
                # care of closing the remaining server connections.
                log.exception("Failed to notify parent session about {0}:",
                              self)