Add base vars and sudo check
This commit is contained in:
parent
c151fd6910
commit
054f5ad80c
8733 changed files with 137813 additions and 15 deletions
|
@ -0,0 +1,32 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
__all__ = []
|
||||
|
||||
|
||||
adapter_host = None
|
||||
"""The host on which adapter is running and listening for incoming connections
|
||||
from the launcher and the servers."""
|
||||
|
||||
channel = None
|
||||
"""DAP message channel to the adapter."""
|
||||
|
||||
|
||||
def connect(host, port):
|
||||
from debugpy.common import log, messaging, sockets
|
||||
from debugpy.launcher import handlers
|
||||
|
||||
global channel, adapter_host
|
||||
assert channel is None
|
||||
assert adapter_host is None
|
||||
|
||||
log.info("Connecting to adapter at {0}:{1}", host, port)
|
||||
|
||||
sock = sockets.create_client()
|
||||
sock.connect((host, port))
|
||||
adapter_host = host
|
||||
|
||||
stream = messaging.JsonIOStream.from_socket(sock, "Adapter")
|
||||
channel = messaging.JsonMessageChannel(stream, handlers=handlers)
|
||||
channel.start()
|
|
@ -0,0 +1,91 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
__all__ = ["main"]
|
||||
|
||||
import locale
|
||||
import signal
|
||||
import sys
|
||||
|
||||
# WARNING: debugpy and submodules must not be imported on top level in this module,
|
||||
# and should be imported locally inside main() instead.
|
||||
|
||||
|
||||
def main():
|
||||
from debugpy import launcher
|
||||
from debugpy.common import log
|
||||
from debugpy.launcher import debuggee
|
||||
|
||||
log.to_file(prefix="debugpy.launcher")
|
||||
log.describe_environment("debugpy.launcher startup environment:")
|
||||
|
||||
if sys.platform == "win32":
|
||||
# For windows, disable exceptions on Ctrl+C - we want to allow the debuggee
|
||||
# process to handle these, or not, as it sees fit. If the debuggee exits
|
||||
# on Ctrl+C, the launcher will also exit, so it doesn't need to observe
|
||||
# the signal directly.
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
|
||||
# Everything before "--" is command line arguments for the launcher itself,
|
||||
# and everything after "--" is command line arguments for the debuggee.
|
||||
log.info("sys.argv before parsing: {0}", sys.argv)
|
||||
sep = sys.argv.index("--")
|
||||
launcher_argv = sys.argv[1:sep]
|
||||
sys.argv[:] = [sys.argv[0]] + sys.argv[sep + 1 :]
|
||||
log.info("sys.argv after patching: {0}", sys.argv)
|
||||
|
||||
# The first argument specifies the host/port on which the adapter is waiting
|
||||
# for launcher to connect. It's either host:port, or just port.
|
||||
adapter = launcher_argv[0]
|
||||
host, sep, port = adapter.partition(":")
|
||||
if not sep:
|
||||
host = "127.0.0.1"
|
||||
port = adapter
|
||||
port = int(port)
|
||||
|
||||
launcher.connect(host, port)
|
||||
launcher.channel.wait()
|
||||
|
||||
if debuggee.process is not None:
|
||||
sys.exit(debuggee.process.returncode)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# debugpy can also be invoked directly rather than via -m. In this case, the first
|
||||
# entry on sys.path is the one added automatically by Python for the directory
|
||||
# containing this file. This means that import debugpy will not work, since we need
|
||||
# the parent directory of debugpy/ to be in sys.path, rather than debugpy/launcher/.
|
||||
#
|
||||
# The other issue is that many other absolute imports will break, because they
|
||||
# will be resolved relative to debugpy/launcher/ - e.g. `import state` will then try
|
||||
# to import debugpy/launcher/state.py.
|
||||
#
|
||||
# To fix both, we need to replace the automatically added entry such that it points
|
||||
# at parent directory of debugpy/ instead of debugpy/launcher, import debugpy with that
|
||||
# in sys.path, and then remove the first entry entry altogether, so that it doesn't
|
||||
# affect any further imports we might do. For example, suppose the user did:
|
||||
#
|
||||
# python /foo/bar/debugpy/launcher ...
|
||||
#
|
||||
# At the beginning of this script, sys.path will contain "/foo/bar/debugpy/launcher"
|
||||
# as the first entry. What we want is to replace it with "/foo/bar', then import
|
||||
# debugpy with that in effect, and then remove the replaced entry before any more
|
||||
# code runs. The imported debugpy module will remain in sys.modules, and thus all
|
||||
# future imports of it or its submodules will resolve accordingly.
|
||||
if "debugpy" not in sys.modules:
|
||||
# Do not use dirname() to walk up - this can be a relative path, e.g. ".".
|
||||
sys.path[0] = sys.path[0] + "/../../"
|
||||
__import__("debugpy")
|
||||
del sys.path[0]
|
||||
|
||||
# Apply OS-global and user-specific locale settings.
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, "")
|
||||
except Exception:
|
||||
# On POSIX, locale is set via environment variables, and this can fail if
|
||||
# those variables reference a non-existing locale. Ignore and continue using
|
||||
# the default "C" locale if so.
|
||||
pass
|
||||
|
||||
main()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
249
venv/lib/python3.8/site-packages/debugpy/launcher/debuggee.py
Normal file
249
venv/lib/python3.8/site-packages/debugpy/launcher/debuggee.py
Normal file
|
@ -0,0 +1,249 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
import atexit
|
||||
import ctypes
|
||||
import os
|
||||
import signal
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
|
||||
from debugpy import launcher
|
||||
from debugpy.common import log, messaging
|
||||
from debugpy.launcher import output
|
||||
|
||||
if sys.platform == "win32":
|
||||
from debugpy.launcher import winapi
|
||||
|
||||
|
||||
process = None
|
||||
"""subprocess.Popen instance for the debuggee process."""
|
||||
|
||||
job_handle = None
|
||||
"""On Windows, the handle for the job object to which the debuggee is assigned."""
|
||||
|
||||
wait_on_exit_predicates = []
|
||||
"""List of functions that determine whether to pause after debuggee process exits.
|
||||
|
||||
Every function is invoked with exit code as the argument. If any of the functions
|
||||
returns True, the launcher pauses and waits for user input before exiting.
|
||||
"""
|
||||
|
||||
|
||||
def describe():
|
||||
return f"Debuggee[PID={process.pid}]"
|
||||
|
||||
|
||||
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(
|
||||
"Couldn't spawn debuggee: {0}\n\nCommand line:{1!r}".format(
|
||||
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),
|
||||
)
|
||||
|
||||
job_info.BasicLimitInformation.LimitFlags |= (
|
||||
# Ensure that the job will be terminated by the OS once the
|
||||
# launcher exits, even if it doesn't terminate the job explicitly.
|
||||
winapi.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
|
||||
|
|
||||
# Allow the debuggee to create its own jobs unrelated to ours.
|
||||
winapi.JOB_OBJECT_LIMIT_BREAKAWAY_OK
|
||||
)
|
||||
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("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")
|
||||
|
||||
|
||||
def kill():
|
||||
if process is None:
|
||||
return
|
||||
|
||||
try:
|
||||
if process.poll() is None:
|
||||
log.info("Killing {0}", describe())
|
||||
# Clean up the process tree
|
||||
if sys.platform == "win32":
|
||||
# On Windows, kill the job object.
|
||||
winapi.kernel32.TerminateJobObject(job_handle, 0)
|
||||
else:
|
||||
# On POSIX, kill the debuggee's process group.
|
||||
os.killpg(process.pid, signal.SIGKILL)
|
||||
except Exception:
|
||||
log.swallow_exception("Failed to kill {0}", describe())
|
||||
|
||||
|
||||
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.swallow_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 _wait_for_user_input():
|
||||
if sys.stdout and sys.stdin and sys.stdin.isatty():
|
||||
from debugpy.common import log
|
||||
|
||||
try:
|
||||
import msvcrt
|
||||
except ImportError:
|
||||
can_getch = False
|
||||
else:
|
||||
can_getch = True
|
||||
|
||||
if can_getch:
|
||||
log.debug("msvcrt available - waiting for user input via getch()")
|
||||
sys.stdout.write("Press any key to continue . . . ")
|
||||
sys.stdout.flush()
|
||||
msvcrt.getch()
|
||||
else:
|
||||
log.debug("msvcrt not available - waiting for user input via read()")
|
||||
sys.stdout.write("Press Enter to continue . . . ")
|
||||
sys.stdout.flush()
|
||||
sys.stdin.read(1)
|
152
venv/lib/python3.8/site-packages/debugpy/launcher/handlers.py
Normal file
152
venv/lib/python3.8/site-packages/debugpy/launcher/handlers.py
Normal file
|
@ -0,0 +1,152 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import debugpy
|
||||
from debugpy import launcher
|
||||
from debugpy.common import json
|
||||
from debugpy.launcher import debuggee
|
||||
|
||||
|
||||
def launch_request(request):
|
||||
debug_options = set(request("debugOptions", json.array(str)))
|
||||
|
||||
# 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}:false and "debugOptions":[{1}] are mutually exclusive',
|
||||
json.repr(prop_name),
|
||||
json.repr(flag_name),
|
||||
)
|
||||
value = True
|
||||
|
||||
return value
|
||||
|
||||
python = request("python", json.array(str, size=(1,)))
|
||||
cmdline = list(python)
|
||||
|
||||
if not request("noDebug", json.default(False)):
|
||||
# see https://github.com/microsoft/debugpy/issues/861
|
||||
if sys.version_info[:2] >= (3, 11):
|
||||
cmdline += ["-X", "frozen_modules=off"]
|
||||
|
||||
port = request("port", int)
|
||||
cmdline += [
|
||||
os.path.dirname(debugpy.__file__),
|
||||
"--connect",
|
||||
launcher.adapter_host + ":" + str(port),
|
||||
]
|
||||
|
||||
if not request("subProcess", True):
|
||||
cmdline += ["--configure-subProcess", "False"]
|
||||
|
||||
qt_mode = request(
|
||||
"qt",
|
||||
json.enum(
|
||||
"none", "auto", "pyside", "pyside2", "pyqt4", "pyqt5", optional=True
|
||||
),
|
||||
)
|
||||
cmdline += ["--configure-qt", qt_mode]
|
||||
|
||||
adapter_access_token = request("adapterAccessToken", str, optional=True)
|
||||
if adapter_access_token != ():
|
||||
cmdline += ["--adapter-access-token", adapter_access_token]
|
||||
|
||||
debugpy_args = request("debugpyArgs", json.array(str))
|
||||
cmdline += debugpy_args
|
||||
|
||||
# Use the copy of arguments that was propagated via the command line rather than
|
||||
# "args" in the request itself, to allow for shell expansion.
|
||||
cmdline += sys.argv[1:]
|
||||
|
||||
process_name = request("processName", sys.executable)
|
||||
|
||||
env = os.environ.copy()
|
||||
env_changes = request("env", json.object((str, type(None))))
|
||||
if sys.platform == "win32":
|
||||
# Environment variables are case-insensitive on Win32, so we need to normalize
|
||||
# both dicts to make sure that env vars specified in the debug configuration
|
||||
# overwrite the global env vars correctly. If debug config has entries that
|
||||
# differ in case only, that's an error.
|
||||
env = {k.upper(): v for k, v in os.environ.items()}
|
||||
new_env_changes = {}
|
||||
for k, v in env_changes.items():
|
||||
k_upper = k.upper()
|
||||
if k_upper in new_env_changes:
|
||||
if new_env_changes[k_upper] == v:
|
||||
continue
|
||||
else:
|
||||
raise request.isnt_valid(
|
||||
'Found duplicate in "env": {0}.'.format(k_upper)
|
||||
)
|
||||
new_env_changes[k_upper] = v
|
||||
env_changes = new_env_changes
|
||||
if "DEBUGPY_TEST" in env:
|
||||
# If we're running as part of a debugpy 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(env_changes)
|
||||
env = {k: v for k, v in env.items() if v is not None}
|
||||
|
||||
if request("gevent", False):
|
||||
env["GEVENT_SUPPORT"] = "True"
|
||||
|
||||
console = request(
|
||||
"console",
|
||||
json.enum(
|
||||
"internalConsole", "integratedTerminal", "externalTerminal", optional=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 = console == "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"):
|
||||
if console == "internalConsole":
|
||||
raise request.isnt_valid(
|
||||
'"waitOnNormalExit" is not supported for "console":"internalConsole"'
|
||||
)
|
||||
debuggee.wait_on_exit_predicates.append(lambda code: code == 0)
|
||||
if property_or_debug_option("waitOnAbnormalExit", "WaitOnAbnormalExit"):
|
||||
if console == "internalConsole":
|
||||
raise request.isnt_valid(
|
||||
'"waitOnAbnormalExit" is not supported for "console":"internalConsole"'
|
||||
)
|
||||
debuggee.wait_on_exit_predicates.append(lambda code: code != 0)
|
||||
|
||||
debuggee.spawn(process_name, cmdline, env, redirect_output)
|
||||
return {}
|
||||
|
||||
|
||||
def terminate_request(request):
|
||||
del debuggee.wait_on_exit_predicates[:]
|
||||
request.respond({})
|
||||
debuggee.kill()
|
||||
|
||||
|
||||
def disconnect():
|
||||
del debuggee.wait_on_exit_predicates[:]
|
||||
debuggee.kill()
|
113
venv/lib/python3.8/site-packages/debugpy/launcher/output.py
Normal file
113
venv/lib/python3.8/site-packages/debugpy/launcher/output.py
Normal file
|
@ -0,0 +1,113 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
import codecs
|
||||
import os
|
||||
import threading
|
||||
|
||||
from debugpy import launcher
|
||||
from debugpy.common import log
|
||||
|
||||
|
||||
class CaptureOutput(object):
|
||||
"""Captures output from the specified file descriptor, and tees it into another
|
||||
file descriptor while generating DAP "output" events for it.
|
||||
"""
|
||||
|
||||
instances = {}
|
||||
"""Keys are output categories, values are CaptureOutput instances."""
|
||||
|
||||
def __init__(self, whose, category, fd, stream):
|
||||
assert category not in self.instances
|
||||
self.instances[category] = self
|
||||
log.info("Capturing {0} of {1}.", category, whose)
|
||||
|
||||
self.category = category
|
||||
self._whose = whose
|
||||
self._fd = fd
|
||||
self._decoder = codecs.getincrementaldecoder("utf-8")(errors="surrogateescape")
|
||||
|
||||
if stream is None:
|
||||
# Can happen if running under pythonw.exe.
|
||||
self._stream = None
|
||||
else:
|
||||
self._stream = stream.buffer
|
||||
encoding = stream.encoding
|
||||
if encoding is None or encoding == "cp65001":
|
||||
encoding = "utf-8"
|
||||
try:
|
||||
self._encode = codecs.getencoder(encoding)
|
||||
except Exception:
|
||||
log.swallow_exception(
|
||||
"Unsupported {0} encoding {1!r}; falling back to UTF-8.",
|
||||
category,
|
||||
encoding,
|
||||
level="warning",
|
||||
)
|
||||
self._encode = codecs.getencoder("utf-8")
|
||||
else:
|
||||
log.info("Using encoding {0!r} for {1}", encoding, category)
|
||||
|
||||
self._worker_thread = threading.Thread(target=self._worker, name=category)
|
||||
self._worker_thread.start()
|
||||
|
||||
def __del__(self):
|
||||
fd = self._fd
|
||||
if fd is not None:
|
||||
try:
|
||||
os.close(fd)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _worker(self):
|
||||
while self._fd is not None:
|
||||
try:
|
||||
s = os.read(self._fd, 0x1000)
|
||||
except Exception:
|
||||
break
|
||||
if not len(s):
|
||||
break
|
||||
self._process_chunk(s)
|
||||
|
||||
# Flush any remaining data in the incremental decoder.
|
||||
self._process_chunk(b"", final=True)
|
||||
|
||||
def _process_chunk(self, s, final=False):
|
||||
s = self._decoder.decode(s, final=final)
|
||||
if len(s) == 0:
|
||||
return
|
||||
|
||||
try:
|
||||
launcher.channel.send_event(
|
||||
"output", {"category": self.category, "output": s.replace("\r\n", "\n")}
|
||||
)
|
||||
except Exception:
|
||||
pass # channel to adapter is already closed
|
||||
|
||||
if self._stream is None:
|
||||
return
|
||||
|
||||
try:
|
||||
s, _ = self._encode(s, "surrogateescape")
|
||||
size = len(s)
|
||||
i = 0
|
||||
while i < size:
|
||||
written = self._stream.write(s[i:])
|
||||
self._stream.flush()
|
||||
if written == 0:
|
||||
# This means that the output stream was closed from the other end.
|
||||
# Do the same to the debuggee, so that it knows as well.
|
||||
os.close(self._fd)
|
||||
self._fd = None
|
||||
break
|
||||
i += written
|
||||
except Exception:
|
||||
log.swallow_exception("Error printing {0!r} to {1}", s, self.category)
|
||||
|
||||
|
||||
def wait_for_remaining_output():
|
||||
"""Waits for all remaining output to be captured and propagated."""
|
||||
for category, instance in CaptureOutput.instances.items():
|
||||
log.info("Waiting for remaining {0} of {1}.", category, instance._whose)
|
||||
instance._worker_thread.join()
|
104
venv/lib/python3.8/site-packages/debugpy/launcher/winapi.py
Normal file
104
venv/lib/python3.8/site-packages/debugpy/launcher/winapi.py
Normal file
|
@ -0,0 +1,104 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
import ctypes
|
||||
from ctypes.wintypes import BOOL, DWORD, HANDLE, LARGE_INTEGER, LPCSTR, UINT
|
||||
|
||||
from debugpy.common import log
|
||||
|
||||
|
||||
JOBOBJECTCLASS = ctypes.c_int
|
||||
LPDWORD = ctypes.POINTER(DWORD)
|
||||
LPVOID = ctypes.c_void_p
|
||||
SIZE_T = ctypes.c_size_t
|
||||
ULONGLONG = ctypes.c_ulonglong
|
||||
|
||||
|
||||
class IO_COUNTERS(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("ReadOperationCount", ULONGLONG),
|
||||
("WriteOperationCount", ULONGLONG),
|
||||
("OtherOperationCount", ULONGLONG),
|
||||
("ReadTransferCount", ULONGLONG),
|
||||
("WriteTransferCount", ULONGLONG),
|
||||
("OtherTransferCount", ULONGLONG),
|
||||
]
|
||||
|
||||
|
||||
class JOBOBJECT_BASIC_LIMIT_INFORMATION(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("PerProcessUserTimeLimit", LARGE_INTEGER),
|
||||
("PerJobUserTimeLimit", LARGE_INTEGER),
|
||||
("LimitFlags", DWORD),
|
||||
("MinimumWorkingSetSize", SIZE_T),
|
||||
("MaximumWorkingSetSize", SIZE_T),
|
||||
("ActiveProcessLimit", DWORD),
|
||||
("Affinity", SIZE_T),
|
||||
("PriorityClass", DWORD),
|
||||
("SchedulingClass", DWORD),
|
||||
]
|
||||
|
||||
|
||||
class JOBOBJECT_EXTENDED_LIMIT_INFORMATION(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("BasicLimitInformation", JOBOBJECT_BASIC_LIMIT_INFORMATION),
|
||||
("IoInfo", IO_COUNTERS),
|
||||
("ProcessMemoryLimit", SIZE_T),
|
||||
("JobMemoryLimit", SIZE_T),
|
||||
("PeakProcessMemoryUsed", SIZE_T),
|
||||
("PeakJobMemoryUsed", SIZE_T),
|
||||
]
|
||||
|
||||
|
||||
JobObjectExtendedLimitInformation = JOBOBJECTCLASS(9)
|
||||
|
||||
JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800
|
||||
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000
|
||||
|
||||
PROCESS_TERMINATE = 0x0001
|
||||
PROCESS_SET_QUOTA = 0x0100
|
||||
|
||||
|
||||
def _errcheck(is_error_result=(lambda result: not result)):
|
||||
def impl(result, func, args):
|
||||
if is_error_result(result):
|
||||
log.debug("{0} returned {1}", func.__name__, result)
|
||||
raise ctypes.WinError()
|
||||
else:
|
||||
return result
|
||||
|
||||
return impl
|
||||
|
||||
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
|
||||
kernel32.AssignProcessToJobObject.errcheck = _errcheck()
|
||||
kernel32.AssignProcessToJobObject.restype = BOOL
|
||||
kernel32.AssignProcessToJobObject.argtypes = (HANDLE, HANDLE)
|
||||
|
||||
kernel32.CreateJobObjectA.errcheck = _errcheck(lambda result: result == 0)
|
||||
kernel32.CreateJobObjectA.restype = HANDLE
|
||||
kernel32.CreateJobObjectA.argtypes = (LPVOID, LPCSTR)
|
||||
|
||||
kernel32.OpenProcess.errcheck = _errcheck(lambda result: result == 0)
|
||||
kernel32.OpenProcess.restype = HANDLE
|
||||
kernel32.OpenProcess.argtypes = (DWORD, BOOL, DWORD)
|
||||
|
||||
kernel32.QueryInformationJobObject.errcheck = _errcheck()
|
||||
kernel32.QueryInformationJobObject.restype = BOOL
|
||||
kernel32.QueryInformationJobObject.argtypes = (
|
||||
HANDLE,
|
||||
JOBOBJECTCLASS,
|
||||
LPVOID,
|
||||
DWORD,
|
||||
LPDWORD,
|
||||
)
|
||||
|
||||
kernel32.SetInformationJobObject.errcheck = _errcheck()
|
||||
kernel32.SetInformationJobObject.restype = BOOL
|
||||
kernel32.SetInformationJobObject.argtypes = (HANDLE, JOBOBJECTCLASS, LPVOID, DWORD)
|
||||
|
||||
kernel32.TerminateJobObject.errcheck = _errcheck()
|
||||
kernel32.TerminateJobObject.restype = BOOL
|
||||
kernel32.TerminateJobObject.argtypes = (HANDLE, UINT)
|
Loading…
Add table
Add a link
Reference in a new issue