Skip to content

Commit

Permalink
Fix #92: Debugging as sudo fails to terminate the debugger when you stop
Browse files Browse the repository at this point in the history
Apply sudo to debugpy.launcher, rather than the debuggee itself.
  • Loading branch information
int19h committed Apr 2, 2020
1 parent 2d35573 commit 32c00bc
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 19 deletions.
29 changes: 28 additions & 1 deletion src/debugpy/adapter/clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,29 @@ def launch_request(self, request):
if self.session.id != 1 or len(servers.connections()):
raise request.cant_handle('"attach" expected')

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

# Launcher doesn't use the command line at all, but we pass the arguments so
# that they show up in the terminal if we're using "runInTerminal".
if "program" in request:
Expand Down Expand Up @@ -277,8 +300,12 @@ def launch_request(self, request):
)
console_title = request("consoleTitle", json.default("Python Debug Console"))

sudo = bool(property_or_debug_option("sudo", "Sudo"))
if sudo and sys.platform == "win32":
raise request.cant_handle('"sudo":true is not supported on Windows.')

servers.serve()
launchers.spawn_debuggee(self.session, request, args, console, console_title)
launchers.spawn_debuggee(self.session, request, args, console, console_title, sudo)

@_start_message_handler
def attach_request(self, request):
Expand Down
13 changes: 10 additions & 3 deletions src/debugpy/adapter/launchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,13 @@ def terminate_debuggee(self):
pass


def spawn_debuggee(session, start_request, args, console, console_title):
cmdline = [sys.executable, os.path.dirname(launcher.__file__)] + args
def spawn_debuggee(session, start_request, args, 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 += [sys.executable, os.path.dirname(launcher.__file__)]
cmdline += args
env = {}

arguments = dict(start_request.arguments)
Expand Down Expand Up @@ -131,7 +136,9 @@ def on_launcher_connected(sock):
except messaging.MessageHandlingError as exc:
exc.propagate(start_request)

if not session.wait_for(lambda: session.launcher, timeout=10):
# 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 10)):
raise start_request.cant_handle("Timed out waiting for launcher to connect")

try:
Expand Down
16 changes: 4 additions & 12 deletions src/debugpy/launcher/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,6 @@ def property_or_debug_option(prop_name, flag_name):

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"
Expand All @@ -55,10 +48,9 @@ def property_or_debug_option(prop_name, flag_name):
)
elif "pythonPath" in request:
python_key = "pythonPath"
python = request(python_key, json.array(unicode, vectorize=True, size=(0,)))
if not len(python):
python = [compat.filename(sys.executable)]
cmdline += python
cmdline = request(python_key, json.array(unicode, vectorize=True, size=(0,)))
if not len(cmdline):
cmdline = [compat.filename(sys.executable)]

if not request("noDebug", json.default(False)):
port = request("port", int)
Expand Down Expand Up @@ -87,7 +79,7 @@ def property_or_debug_option(prop_name, flag_name):
if "code" in request:
code = request("code", json.array(unicode, vectorize=True, size=(1,)))
cmdline += ["-c", "\n".join(code)]
process_name = python[0]
process_name = cmdline[0]

num_targets = len([x for x in (program, module, code) if x != ()])
if num_targets == 0:
Expand Down
3 changes: 1 addition & 2 deletions tests/debug/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,8 +492,7 @@ def _process_request(self, request):
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.env.update(env)
self.spawn_debuggee(args, cwd, exe=exe)
return {}
except OSError as exc:
Expand Down
5 changes: 4 additions & 1 deletion tests/debugpy/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def test_sudo(pyfile, tmpdir, run, target):
sudo = tmpdir / "sudo"
sudo.write(
"""#!/bin/sh
if [ "$1" = "-E" ]; then shift; fi
exec env DEBUGPY_SUDO=1 "$@"
"""
)
Expand All @@ -116,7 +117,9 @@ def code_to_debug():

with debug.Session() as session:
session.config["sudo"] = True
session.config.env["PATH"] = tmpdir.strpath + ":" + os.environ["PATH"]
session.spawn_adapter.env["PATH"] = session.spawn_debuggee.env["PATH"] = (
tmpdir.strpath + ":" + os.environ["PATH"]
)

backchannel = session.open_backchannel()
with run(session, target(code_to_debug)):
Expand Down

0 comments on commit 32c00bc

Please sign in to comment.