def finalize(self, why, terminate_debuggee=False): """Finalizes the debug session. If the server is present, sends "disconnect" request with "terminateDebuggee" set as specified) request to it; waits for it to disconnect, allowing any remaining messages from it to be handled; and closes the server channel. If the launcher is present, sends "terminate" request to it, regardless of the value of terminate; waits for it to disconnect, allowing any remaining messages from it to be handled; and closes the launcher channel. If the IDE is present, sends "terminated" event to it. """ if self.is_finalizing: return self.is_finalizing = True log.info("{0}; finalizing {1}.", why, self) try: self._finalize(why, terminate_debuggee) except Exception: # Finalization should never fail, and if it does, the session is in an # indeterminate and likely unrecoverable state, so just fail fast. log.exception("Fatal error while finalizing {0}", self) os._exit(1) log.info("{0} finalized.", self)
def accept_worker(): log.info( "Listening for incoming connection from {0} on port {1}...", self, self.port, ) server_socket = self._server_socket if server_socket is None: return # concurrent close() try: sock, _ = server_socket.accept() except socket.timeout: if self._server_socket is None: return else: raise log.exception("Timed out waiting for {0} to connect", self) except Exception: if self._server_socket is None: return else: raise log.exception("Error accepting connection for {0}:", self) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) log.info("Incoming connection from {0} accepted.", self) self._socket = sock self._setup_stream()
def wait_for_exit(): try: code = process.wait() if sys.platform != "win32" and code < 0: # On POSIX, if the process was terminated by a signal, Popen will use # a negative returncode to indicate that - but the actual exit code of # the process is always an unsigned number, and can be determined by # taking the lowest 8 bits of that negative returncode. code &= 0xFF except Exception: log.exception("Couldn't determine process exit code:") code = -1 log.info("{0} exited with code {1}", describe(), code) output.wait_for_remaining_output() # Determine whether we should wait or not before sending "exited", so that any # follow-up "terminate" requests don't affect the predicates. should_wait = any(pred(code) for pred in wait_on_exit_predicates) try: launcher.channel.send_event("exited", {"exitCode": code}) except Exception: pass if should_wait: _wait_for_user_input() try: launcher.channel.send_event("terminated") except Exception: pass
def main(): original_argv = list(sys.argv) try: sys.argv[1:] = parse(sys.argv[1:]) except Exception as ex: print(HELP + "\nError: " + str(ex), file=sys.stderr) sys.exit(2) log.to_file(prefix="ptvsd.server") log.describe_environment("ptvsd.server startup environment:") log.info( "sys.argv before parsing: {0!r}\n" " after parsing: {1!r}", original_argv, sys.argv, ) try: run = { "file": run_file, "module": run_module, "code": run_code, "pid": attach_to_pid, }[options.target_kind] run() except SystemExit as ex: log.exception("Debuggee exited via SystemExit: {0!r}", ex.code, level="debug") raise
def inject(pid, ptvsd_args): host, port = Connection.listener.getsockname() cmdline = [ sys.executable, compat.filename(os.path.dirname(ptvsd.__file__)), "--client", "--host", host, "--port", str(port), ] cmdline += ptvsd_args cmdline += ["--pid", str(pid)] log.info("Spawning attach-to-PID debugger injector: {0!r}", cmdline) try: subprocess.Popen( cmdline, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) except Exception as exc: log.exception( "Failed to inject debug server into process with PID={0}", pid) raise messaging.MessageHandlingError( "Failed to inject debug server into process with PID={0}: {1}", pid, exc)
def _finalize(self, why, terminate_debuggee): if self.server and self.server.is_connected: try: self.server.channel.request( "disconnect", {"terminateDebuggee": terminate_debuggee}) except Exception: pass try: self.server.channel.close() except Exception: log.exception() # Wait until the server message queue fully drains - there won't be any # more events after close(), but there may still be pending responses. log.info("{0} waiting for {1} to disconnect...", self, self.server) if not self.wait_for(lambda: not self.server.is_connected, timeout=5): log.warning("{0} timed out waiting for {1} to disconnect.", self, self.server) if self.launcher and self.launcher.is_connected: # If there was a server, we just disconnected from it above, which should # cause the debuggee process to exit - so let's wait for that first. if self.server: log.info('{0} waiting for "exited" event...', self) if not self.wait_for( lambda: self.launcher.exit_code is not None, timeout=5): log.warning('{0} timed out waiting for "exited" event.', self) # Terminate the debuggee process if it's still alive for any reason - # whether it's because there was no server to handle graceful shutdown, # or because the server couldn't handle it for some reason. try: self.launcher.channel.request("terminate") except Exception: pass # Wait until the launcher message queue fully drains. log.info("{0} waiting for {1} to disconnect...", self, self.launcher) if not self.wait_for(lambda: not self.launcher.is_connected, timeout=5): log.warning("{0} timed out waiting for {1} to disconnect.", self, self.launcher) try: self.launcher.channel.close() except Exception: log.exception() # Tell the IDE that debugging is over, but don't close the channel until it # tells us to, via the "disconnect" request. if self.ide and self.ide.is_connected: try: self.ide.channel.send_event("terminated") except Exception: pass
def inject_server(self, pid, ptvsd_args): with self.accept_connection_from_server() as (host, port): cmdline = [ sys.executable, compat.filename(os.path.dirname(ptvsd.__file__)), "--client", "--host", host, "--port", str(port), ] cmdline += ptvsd_args cmdline += ["--pid", str(pid)] log.info("{0} spawning attach-to-PID debugger injector: {1!r}", self, cmdline) try: subprocess.Popen( cmdline, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) except Exception as exc: log.exception("{0} failed to inject debugger", self) raise messaging.MessageHandlingError( fmt("Failed to inject debugger: {0}", exc))
def wait_for_exit(): from ptvsd.launcher import adapter, output try: code = process.wait() if platform.system() != "Windows" and code < 0: # On POSIX, if the process was terminated by a signal, Popen will use # a negative returncode to indicate that - but the actual exit code of # the process is always an unsigned number, and can be determined by # taking the lowest 8 bits of that negative returncode. code &= 0xFF except Exception: log.exception("Couldn't determine process exit code:") code = -1 log.info("{0} exited with code {1}", describe(), code) output.wait_for_remaining_output() try: adapter.channel.send_event("exited", {"exitCode": code}) except Exception: pass if any(pred(code) for pred in wait_on_exit_predicates): _wait_for_user_input() try: adapter.channel.send_event("terminated") except Exception: pass
def __exit__(self, exc_type, exc_val, exc_tb): log.info("Ending {0}.", self) if self.timeline.is_frozen: self.timeline.unfreeze() # Only wait for exit if there was no exception in the test - if there was one, # the debuggee might still be waiting for further requests. if exc_type is None: # If expected_exit_code is set to None, the debuggee is not expected to # exit after this Session is closed (e.g. because another Session will # attach to it later on). if self.expected_exit_code is not None: self.wait_for_exit() else: # Log the error, in case another one happens during shutdown. log.exception(exc_info=(exc_type, exc_val, exc_tb)) if exc_type is None: self.disconnect() self.timeline.close() else: # If there was an exception, don't try to send any more messages to avoid # spamming log with irrelevant entries - just close the channel and kill # all the processes immediately. Don't close or finalize the timeline, # either, since it'll likely have unobserved events in it. if self.adapter is not None: log.info("Killing {0}.", self.adapter_id) try: self.adapter.kill() except Exception: pass if self.debuggee is not None: log.info("Killing {0}.", self.debuggee_id) try: self.debuggee.kill() except Exception: pass self.disconnect(force=True) if self.adapter_endpoints is not None and self.expected_exit_code is not None: log.info("Waiting for {0} to close listener ports ...", self.adapter_id) while self.adapter_endpoints.check(): time.sleep(0.1) if self.adapter is not None: log.info( "Waiting for {0} with PID={1} to exit.", self.adapter_id, self.adapter.pid, ) self.adapter.wait() watchdog.unregister_spawn(self.adapter.pid, self.adapter_id) self.adapter = None if self.backchannel is not None: self.backchannel.close() self.backchannel = None
def delete_listener_file(): log.info("Listener ports closed; deleting {0!r}", listener_file) try: os.remove(listener_file) except Exception: log.exception("Failed to delete {0!r}", listener_file, level="warning")
def _finalize(self, why, terminate_debuggee): # If the IDE started a session, and then disconnected before issuing "launch" # or "attach", the main thread will be blocked waiting for the first server # connection to come in - unblock it, so that we can exit. servers.dont_wait_for_first_connection() if self.server: if self.server.is_connected: if terminate_debuggee and self.launcher and self.launcher.is_connected: # If we were specifically asked to terminate the debuggee, and we # can ask the launcher to kill it, do so instead of disconnecting # from the server to prevent debuggee from running any more code. self.launcher.terminate_debuggee() else: # Otherwise, let the server handle it the best it can. try: self.server.channel.request( "disconnect", {"terminateDebuggee": terminate_debuggee}) except Exception: pass self.server.detach_from_session() if self.launcher and self.launcher.is_connected: # If there was a server, we just disconnected from it above, which should # cause the debuggee process to exit - so let's wait for that first. if self.server: log.info('{0} waiting for "exited" event...', self) if not self.wait_for( lambda: self.launcher.exit_code is not None, timeout=5): log.warning('{0} timed out waiting for "exited" event.', self) # Terminate the debuggee process if it's still alive for any reason - # whether it's because there was no server to handle graceful shutdown, # or because the server couldn't handle it for some reason. self.launcher.terminate_debuggee() # Wait until the launcher message queue fully drains. There is no timeout # here, because the final "terminated" event will only come after reading # user input in wait-on-exit scenarios. log.info("{0} waiting for {1} to disconnect...", self, self.launcher) self.wait_for(lambda: not self.launcher.is_connected) try: self.launcher.channel.close() except Exception: log.exception() # Tell the IDE that debugging is over, but don't close the channel until it # tells us to, via the "disconnect" request. if self.ide and self.ide.is_connected: try: self.ide.channel.send_event("terminated") except Exception: pass
def kill(): if process is None: return try: if process.poll() is None: log.info("Killing {0}", describe()) process.kill() except Exception: log.exception("Failed to kill {0}", describe())
def stop(): if _stream is None: return try: _invoke("stop") _stream.close() except Exception: log.exception()
def _worker(self, func, *args, **kwargs): try: self.request = func(self.url, *args, **kwargs) except Exception as exc: if self.log_errors: log.exception("{0} failed:", self) self.exception = exc else: log.info("{0} --> {1} {2}", self, self.request.status_code, self.request.reason)
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)
def inject(pid, ptvsd_args): host, port = Connection.listener.getsockname() cmdline = [ sys.executable, compat.filename(os.path.dirname(ptvsd.__file__)), "--client", "--host", host, "--port", str(port), ] if adapter.access_token is not None: cmdline += ["--client-access-token", adapter.access_token] cmdline += ptvsd_args cmdline += ["--pid", str(pid)] log.info("Spawning attach-to-PID debugger injector: {0!r}", cmdline) try: injector = subprocess.Popen( cmdline, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) except Exception as exc: log.exception("Failed to inject debug server into process with PID={0}", pid) raise messaging.MessageHandlingError( fmt( "Failed to inject debug server into process with PID={0}: {1}", pid, exc ) ) # We need to capture the output of the injector - otherwise it can get blocked # on a write() syscall when it tries to print something. def capture_output(): while True: line = injector.stdout.readline() if not line: break log.info("Injector[PID={0}] output:\n{1}", pid, line.rstrip()) log.info("Injector[PID={0}] exited.", pid) thread = threading.Thread( target=capture_output, name=fmt("Injector[PID={0}] output", pid) ) thread.daemon = True thread.start()
def run_module(): # Add current directory to path, like Python itself does for -m. This must # be in place before trying to use find_spec below to resolve submodules. sys.path.insert(0, "") # We want to do the same thing that run_module() would do here, without # actually invoking it. On Python 3, it's exposed as a public API, but # on Python 2, we have to invoke a private function in runpy for this. # Either way, if it fails to resolve for any reason, just leave argv as is. argv_0 = sys.argv[0] try: if sys.version_info >= (3,): from importlib.util import find_spec spec = find_spec(options.target) if spec is not None: argv_0 = spec.origin else: _, _, _, argv_0 = runpy._get_module_details(options.target) except Exception: log.exception("Error determining module path for sys.argv") setup_debug_server(argv_0) # On Python 2, module name must be a non-Unicode string, because it ends up # a part of module's __package__, and Python will refuse to run the module # if __package__ is Unicode. target = ( compat.filename_bytes(options.target) if sys.version_info < (3,) else options.target ) log.describe_environment("Pre-launch environment:") log.info("Running module {0!r}", target) # Docs say that runpy.run_module is equivalent to -m, but it's not actually # the case for packages - -m sets __name__ to "__main__", but run_module sets # it to "pkg.__main__". This breaks everything that uses the standard pattern # __name__ == "__main__" to detect being run as a CLI app. On the other hand, # runpy._run_module_as_main is a private function that actually implements -m. try: run_module_as_main = runpy._run_module_as_main except AttributeError: log.warning("runpy._run_module_as_main is missing, falling back to run_module.") runpy.run_module(target, alter_sys=True) else: run_module_as_main(target, alter_argv=True)
def debug(address, log_dir=None, multiprocess=True): if log_dir: log.log_dir = log_dir log.to_file(prefix="ptvsd.server") log.describe_environment("ptvsd.server debug start environment:") log.debug("{0}{1!r}", func.__name__, (address, log_dir, multiprocess)) if is_attached(): log.info("{0}() ignored - already attached.", func.__name__) return options.host, options.port # Ensure port is int if address is not options: host, port = address options.host, options.port = (host, int(port)) if multiprocess is not options: options.multiprocess = multiprocess ptvsd_path, _, _ = get_abs_path_real_path_and_base_from_file(ptvsd.__file__) ptvsd_path = os.path.dirname(ptvsd_path) start_patterns = (ptvsd_path,) end_patterns = ("ptvsd_launcher.py",) log.info( "Won't trace filenames starting with: {0!j}\n" "Won't trace filenames ending with: {1!j}", start_patterns, end_patterns, ) try: return func(start_patterns, end_patterns) except Exception: raise log.exception("{0}() failed:", func.__name__, level="info")
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")
def _dump_worker_log(command, problem, exc_info=None): reason = fmt("{0}.{1}() {2}", _name, command, problem) if _worker_log_filename is None: reason += ", but there is no log." else: try: with open(_worker_log_filename) as f: worker_log = f.read() except Exception: reason += fmt(", but log {0} could not be retrieved.", _worker_log_filename) else: reason += fmt("; watchdog worker process log:\n\n{0}", worker_log) if exc_info is None: log.error("{0}", reason) else: log.exception("{0}", reason, exc_info=exc_info) return reason
def dump(why): assert why pydevd_debug_file = os.environ.get("PYDEVD_DEBUG_FILE") if not pydevd_debug_file: return try: f = open(pydevd_debug_file) with f: pydevd_log = f.read() except Exception: log.exception( "Test {0}, but pydevd log {1} could not be retrieved.", why, pydevd_debug_file, ) return log.info("Test {0}; pydevd log:\n\n{1}", why, pydevd_log)
def wait_for_exit(): from ptvsd.launcher import adapter, output try: code = process.wait() except Exception: log.exception("Couldn't determine process exit code:") code = -1 log.info("{0} exited with code {1}", describe(), code) output.wait_for_remaining_output() try: adapter.channel.send_event("exited", {"exitCode": code}) except Exception: pass if any(pred(code) for pred in wait_on_exit_predicates): _wait_for_user_input() try: adapter.channel.send_event("terminated") except Exception: pass
def wait_until_port_is_listening(port, interval=1, max_attempts=1000): """Blocks until the specified TCP port on localhost is listening, and can be connected to. Tries to connect to the port periodically, and repeats until connection succeeds. Connection is immediately closed before returning. """ for i in compat.xrange(1, max_attempts + 1): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: log.info("Probing localhost:{0} (attempt {1})...", port, i) sock.connect(("localhost", port)) except socket.error: # The first attempt will almost always fail, because the port isn't # open yet. But if it keeps failing after that, we want to know why. if i > 1: log.exception() time.sleep(interval) else: log.info("localhost:{0} is listening - server is up!", port) return finally: sock.close()
def accept_worker(): log.info( "Listening for incoming connection from {0} on port {1}...", self, self.port, ) try: self._socket, _ = self._server_socket.accept() except socket.timeout: raise log.exception("Timed out waiting for {0} to connect", self) self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) log.info("Incoming connection from {0} accepted.", self) self._setup_stream()
def listen(cls, host=None, port=0, timeout=None, name=None): """Accepts TCP connections on the specified host and port, and creates a new instance of this class wrapping every accepted socket. """ if name is None: name = cls.__name__ assert cls.listener is None try: cls.listener = create_server(host, port, timeout) except Exception: raise log.exception( "Error listening for incoming {0} connections on {1}:{2}:", name, host, port, ) host, port = cls.listener.getsockname() log.info("Listening for incoming {0} connections on {1}:{2}...", name, host, port) def accept_worker(): while True: try: sock, (other_host, other_port) = cls.listener.accept() except OSError: # Listener socket has been closed. break log.info( "Accepted incoming {0} connection from {1}:{2}.", name, other_host, other_port, ) cls(sock) thread = threading.Thread(target=accept_worker) thread.daemon = True thread.pydev_do_not_trace = True thread.is_pydev_daemon_thread = True thread.start() return host, port
def stop_listening(): try: IDE.listener.close() except Exception: log.exception(level="warning")
def spawn(process_name, cmdline, cwd, env, redirect_output): log.info( "Spawning debuggee process:\n\n" "Current directory: {0!j}\n\n" "Command line: {1!j}\n\n" "Environment variables: {2!j}\n\n", cwd, cmdline, env, ) close_fds = set() try: if redirect_output: # subprocess.PIPE behavior can vary substantially depending on Python version # and platform; using our own pipes keeps it simple, predictable, and fast. stdout_r, stdout_w = os.pipe() stderr_r, stderr_w = os.pipe() close_fds |= {stdout_r, stdout_w, stderr_r, stderr_w} kwargs = dict(stdout=stdout_w, stderr=stderr_w) else: kwargs = {} try: global process process = subprocess.Popen(cmdline, cwd=cwd, env=env, bufsize=0, **kwargs) except Exception as exc: raise messaging.MessageHandlingError( fmt("Couldn't spawn debuggee: {0}\n\nCommand line:{1!r}", exc, cmdline)) log.info("Spawned {0}.", describe()) atexit.register(kill) launcher.channel.send_event( "process", { "startMethod": "launch", "isLocalProcess": True, "systemProcessId": process.pid, "name": process_name, "pointerSize": struct.calcsize(compat.force_str("P")) * 8, }, ) if redirect_output: for category, fd, tee in [ ("stdout", stdout_r, sys.stdout), ("stderr", stderr_r, sys.stderr), ]: output.CaptureOutput(describe(), category, fd, tee) close_fds.remove(fd) wait_thread = threading.Thread(target=wait_for_exit, name="wait_for_exit()") wait_thread.daemon = True wait_thread.start() finally: for fd in close_fds: try: os.close(fd) except Exception: log.exception()
def option(name, type, *args): try: return type(os.environ.pop(name, *args)) except Exception: raise log.exception("Error parsing {0!r}:", name)
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)
def attach(host, port, client, log_dir=None): try: import sys if 'threading' not in sys.modules: try: def on_warn(msg): print(msg, file=sys.stderr) def on_exception(msg): print(msg, file=sys.stderr) def on_critical(msg): print(msg, file=sys.stderr) pydevd_attach_to_process_path = os.path.join( _ptvsd_dir, 'ptvsd', '_vendored', 'pydevd', 'pydevd_attach_to_process') assert os.path.exists(pydevd_attach_to_process_path) sys.path.insert(0, pydevd_attach_to_process_path) # NOTE: that it's not a part of the pydevd PYTHONPATH import attach_script attach_script.fix_main_thread_id(on_warn=on_warn, on_exception=on_exception, on_critical=on_critical) # NOTE: At this point it should be safe to remove this. sys.path.remove(pydevd_attach_to_process_path) except: import traceback traceback.print_exc() raise sys.path.insert(0, _ptvsd_dir) import ptvsd # NOTE: Don't do sys.path.remove here it will remove all instances of that path # and the user may have set that to ptvsd path via PYTHONPATH assert sys.path[0] == _ptvsd_dir del sys.path[0] from ptvsd.common import options as common_opts from ptvsd.server import options if log_dir is not None: common_opts.log_dir = log_dir options.client = client options.host = host options.port = port if options.client: ptvsd.attach((options.host, options.port)) else: ptvsd.enable_attach((options.host, options.port)) from ptvsd.common import log log.info("Debugger successfully injected") except: import traceback traceback.print_exc() raise log.exception()