def spawn(process_name, cmdline, env, redirect_output): log.info( "Spawning debuggee process:\n\n" "Command line: {0!r}\n\n" "Environment variables: {1!r}\n\n", 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, 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.swallow_exception()
def inject(pid, debugpy_args): host, port = listener.getsockname() cmdline = [ sys.executable, compat.filename(os.path.dirname(debugpy.__file__)), "--connect", host + ":" + str(port), ] if adapter.access_token is not None: cmdline += ["--adapter-access-token", adapter.access_token] cmdline += debugpy_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.swallow_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 require(self, *keys): for key in keys: if not self[key]: raise messaging.MessageHandlingError( fmt("{0} does not have capability {1!j}", self.component, key) )
def spawn(process_name, cmdline, env, redirect_output): log.info( "Spawning debuggee process:\n\n" "Command line: {0!r}\n\n" "Environment variables: {1!r}\n\n", 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 = {} if sys.platform != "win32": def preexec_fn(): try: # Start the debuggee in a new process group, so that the launcher can # kill the entire process tree later. os.setpgrp() # Make the new process group the foreground group in its session, so # that it can interact with the terminal. The debuggee will receive # SIGTTOU when tcsetpgrp() is called, and must ignore it. old_handler = signal.signal(signal.SIGTTOU, signal.SIG_IGN) try: tty = os.open("/dev/tty", os.O_RDWR) try: os.tcsetpgrp(tty, os.getpgrp()) finally: os.close(tty) finally: signal.signal(signal.SIGTTOU, old_handler) except Exception: # Not an error - /dev/tty doesn't work when there's no terminal. log.swallow_exception( "Failed to set up process group", level="info" ) kwargs.update(preexec_fn=preexec_fn) try: global process process = subprocess.Popen(cmdline, 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()) if sys.platform == "win32": # Assign the debuggee to a new job object, so that the launcher can kill # the entire process tree later. try: global job_handle job_handle = winapi.kernel32.CreateJobObjectA(None, None) job_info = winapi.JOBOBJECT_EXTENDED_LIMIT_INFORMATION() job_info_size = winapi.DWORD(ctypes.sizeof(job_info)) winapi.kernel32.QueryInformationJobObject( job_handle, winapi.JobObjectExtendedLimitInformation, ctypes.pointer(job_info), job_info_size, ctypes.pointer(job_info_size), ) # Setting this flag ensures that the job will be terminated by the OS once the # launcher exits, even if it doesn't terminate the job explicitly. job_info.BasicLimitInformation.LimitFlags |= ( winapi.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE ) winapi.kernel32.SetInformationJobObject( job_handle, winapi.JobObjectExtendedLimitInformation, ctypes.pointer(job_info), job_info_size, ) process_handle = winapi.kernel32.OpenProcess( winapi.PROCESS_TERMINATE | winapi.PROCESS_SET_QUOTA, False, process.pid, ) winapi.kernel32.AssignProcessToJobObject(job_handle, process_handle) except Exception: log.swallow_exception("Failed to set up job object", level="warning") 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.swallow_exception(level="warning")