commit b758ee7527ef5d8007b522f039d10885318df91b
parent d5945624a8829d477446f440783dd745458f7325
Author: Yannis Juglaret <yjuglaret@mozilla.com>
Date: Mon, 5 Jan 2026 09:15:59 +0000
Bug 1518065 - Parse --debugger-args using shell syntax. r=mozbase-reviewers,perftest-reviewers,geckodriver-reviewers,jgraham,sparky
Our docs claim that the following is valid:
./mach run --debug --debugger-args "-ex 'show args'"
But currently we fail to handle that case properly.
This patch addresses this by making get_debugger_info parse debuggerArgs
using shell-like syntax, rather than just splitting at spaces.
We also export a new helper function prepend_debugger_args and use it
as much as possible to avoid code duplication, on the code paths that
make the most straightforward use of mozdebug. This fixes a few
duplicate code paths where we intended to handle shell-like syntax but
the code was wrong.
Differential Revision: https://phabricator.services.mozilla.com/D275525
Diffstat:
5 files changed, 66 insertions(+), 162 deletions(-)
diff --git a/python/mozbuild/mozbuild/mach_commands.py b/python/mozbuild/mozbuild/mach_commands.py
@@ -22,6 +22,7 @@ from os import path
from pathlib import Path
import mozpack.path as mozpath
+import mozshellutil
from gtest.reports import AggregatedGTestReport
from gtest.suites import get_gtest_suites, suite_filters
from mach.decorators import (
@@ -30,6 +31,7 @@ from mach.decorators import (
CommandArgumentGroup,
SubCommand,
)
+from mozdebug import prepend_debugger_args
from mozfile import load_source
from mozbuild.base import (
@@ -1095,7 +1097,7 @@ def gtest(
is_debugging = debug or debugger or debugger_args
if is_debugging:
- args = _prepend_debugger_args(args, debugger, debugger_args)
+ args = prepend_debugger_args(args, debugger, debugger_args)
if not args:
return 1
@@ -2165,13 +2167,13 @@ process attach {continue_flag}-p {pid!s}
)
)
- our_debugger_args = "-s %s" % tmp_lldb_start_script
+ our_debugger_args = mozshellutil.quote("-s", tmp_lldb_start_script)
if debugger_args:
full_debugger_args = " ".join([debugger_args, our_debugger_args])
else:
full_debugger_args = our_debugger_args
- args = _prepend_debugger_args([], debugger, full_debugger_args)
+ args = prepend_debugger_args([], debugger, full_debugger_args)
if not args:
return 1
@@ -2238,25 +2240,10 @@ def _run_jsshell(command_context, params, debug, debugger, debugger_args):
if "INSIDE_EMACS" in os.environ:
command_context.log_manager.terminal_handler.setLevel(logging.WARNING)
- import mozdebug
-
- if not debugger:
- # No debugger name was provided. Look for the default ones on
- # current OS.
- debugger = mozdebug.get_default_debugger_name(
- mozdebug.DebuggerSearch.KeepLooking
- )
-
- if debugger:
- debuggerInfo = mozdebug.get_debugger_info(debugger, debugger_args)
-
- if not debugger or not debuggerInfo:
- print("Could not find a suitable debugger in your PATH.")
+ args = prepend_debugger_args(args, debugger, debugger_args)
+ if not args:
return 1
- # Prepend the debugger args.
- args = [debuggerInfo.path] + debuggerInfo.args + args
-
return command_context.run_process(
args=args, ensure_exit_code=False, pass_thru=True, append_env=extra_env
)
@@ -2465,39 +2452,10 @@ def _run_desktop(
if "INSIDE_EMACS" in os.environ:
command_context.log_manager.terminal_handler.setLevel(logging.WARNING)
- import mozdebug
-
- if not debugger:
- # No debugger name was provided. Look for the default ones on
- # current OS.
- debugger = mozdebug.get_default_debugger_name(
- mozdebug.DebuggerSearch.KeepLooking
- )
-
- if debugger:
- debuggerInfo = mozdebug.get_debugger_info(debugger, debugger_args)
-
- if not debugger or not debuggerInfo:
- print("Could not find a suitable debugger in your PATH.")
+ args = prepend_debugger_args(args, debugger, debugger_args)
+ if not args:
return 1
- # Parameters come from the CLI. We need to convert them before
- # their use.
- if debugger_args:
- import mozshellutil
-
- try:
- debugger_args = mozshellutil.split(debugger_args)
- except mozshellutil.MetaCharacterException as e:
- print(
- "The --debugger-args you passed require a real shell to parse them."
- )
- print("(We can't handle the %r character.)" % e.char)
- return 1
-
- # Prepend the debugger args.
- args = [debuggerInfo.path] + debuggerInfo.args + args
-
if dmd:
dmd_params = []
@@ -3954,49 +3912,6 @@ def repackage_single_locales(command_context, verbose=False, locales=[], dest=No
return 0
-def _prepend_debugger_args(args, debugger, debugger_args):
- """
- Given an array with program arguments, prepend arguments to run it under a
- debugger.
-
- :param args: The executable and arguments used to run the process normally.
- :param debugger: The debugger to use, or empty to use the default debugger.
- :param debugger_args: Any additional parameters to pass to the debugger.
- """
-
- import mozdebug
-
- if not debugger:
- # No debugger name was provided. Look for the default ones on
- # current OS.
- debugger = mozdebug.get_default_debugger_name(
- mozdebug.DebuggerSearch.KeepLooking
- )
-
- if debugger:
- debuggerInfo = mozdebug.get_debugger_info(debugger, debugger_args)
-
- if not debugger or not debuggerInfo:
- print("Could not find a suitable debugger in your PATH.")
- return None
-
- # Parameters come from the CLI. We need to convert them before
- # their use.
- if debugger_args:
- import mozshellutil
-
- try:
- debugger_args = mozshellutil.split(debugger_args)
- except mozshellutil.MetaCharacterException as e:
- print("The --debugger_args you passed require a real shell to parse them.")
- print("(We can't handle the %r character.)" % e.char)
- return None
-
- # Prepend the debugger args.
- args = [debuggerInfo.path] + debuggerInfo.args + args
- return args
-
-
@SubCommand(
"repackage",
"flatpak",
diff --git a/testing/geckodriver/mach_commands.py b/testing/geckodriver/mach_commands.py
@@ -7,6 +7,7 @@ import os
from mach.decorators import Command, CommandArgument, CommandArgumentGroup
from mozbuild.base import BinaryNotFoundException
+from mozdebug import prepend_debugger_args
@Command(
@@ -85,37 +86,9 @@ def run(command_context, binary, params, debug, debugger, debugger_args):
if "INSIDE_EMACS" in os.environ:
command_context.log_manager.terminal_handler.setLevel(logging.WARNING)
- import mozdebug
-
- if not debugger:
- # No debugger name was provided. Look for the default ones on
- # current OS.
- debugger = mozdebug.get_default_debugger_name(
- mozdebug.DebuggerSearch.KeepLooking
- )
-
- if debugger:
- debuggerInfo = mozdebug.get_debugger_info(debugger, debugger_args)
- if not debuggerInfo:
- print("Could not find a suitable debugger in your PATH.")
- return 1
-
- # Parameters come from the CLI. We need to convert them before
- # their use.
- if debugger_args:
- import mozshellutil
-
- try:
- debugger_args = mozshellutil.split(debugger_args)
- except mozshellutil.MetaCharacterException as e:
- print(
- "The --debugger-args you passed require a real shell to parse them."
- )
- print("(We can't handle the %r character.)" % e.char)
- return 1
-
- # Prepend the debugger args.
- args = [debuggerInfo.path] + debuggerInfo.args + args
+ args = prepend_debugger_args(args, debugger, debugger_args)
+ if not args:
+ return 1
return command_context.run_process(
args=args, ensure_exit_code=False, pass_thru=True
diff --git a/testing/mozbase/mozdebug/mozdebug/__init__.py b/testing/mozbase/mozdebug/mozdebug/__init__.py
@@ -24,7 +24,7 @@ debugger-specific arguments:
processArgs = [self.debuggerInfo.path] + self.debuggerInfo.args
processArgs.append(debuggeePath)
- run_process(args, ...)
+ run_process(processArgs, ...)
"""
from .mozdebug import *
diff --git a/testing/mozbase/mozdebug/mozdebug/mozdebug.py b/testing/mozbase/mozdebug/mozdebug/mozdebug.py
@@ -12,6 +12,7 @@ from collections import namedtuple
from subprocess import check_output
import mozinfo
+import mozshellutil
__all__ = [
"get_debugger_info",
@@ -19,6 +20,7 @@ __all__ = [
"DebuggerSearch",
"get_default_valgrind_args",
"DebuggerInfo",
+ "prepend_debugger_args",
]
"""
@@ -139,8 +141,10 @@ def get_debugger_info(debugger, debuggerArgs=None, debuggerInteractive=False):
:param debugger: The name of the debugger.
:param debuggerArgs: If specified, it's the arguments to pass to the debugger,
- as a string. Any debugger-specific separator arguments are appended after
- these arguments.
+ as a string. This string will get split using shell-like syntax so that
+ arguments can contain spaces, e.g. passing "-ex 'show args'" really means a
+ first argument of "-ex" followed by a second argument of "show args". Any
+ debugger-specific separator arguments are appended after these arguments.
:param debuggerInteractive: If specified, forces the debugger to be interactive.
"""
@@ -168,13 +172,13 @@ def get_debugger_info(debugger, debuggerArgs=None, debuggerInteractive=False):
debuggerPath = debugger
if not debuggerPath:
- print("Error: Could not find debugger %s." % debugger)
+ print(f"Error: Could not find debugger {debugger!s}.")
print("Is it installed? Is it in your PATH?")
return None
debuggerName = os.path.basename(debuggerPath).lower()
- def get_debugger_info(type, default):
+ def _get_debugger_info(type, default):
if debuggerName in _DEBUGGER_INFO and type in _DEBUGGER_INFO[debuggerName]:
return _DEBUGGER_INFO[debuggerName][type]
return default
@@ -183,13 +187,20 @@ def get_debugger_info(debugger, debuggerArgs=None, debuggerInteractive=False):
debugger_arguments = []
if debuggerArgs:
- # Append the provided debugger arguments at the end of the arguments list.
- debugger_arguments += debuggerArgs.split()
+ try:
+ # Append the provided debugger arguments at the start of the arguments list.
+ debugger_arguments += mozshellutil.split(debuggerArgs)
+ except mozshellutil.MetaCharacterException as e:
+ print(
+ "Error: The --debugger-args you passed require a real shell to parse them."
+ )
+ print(f"(We can't handle the {e.char!r} character.)")
+ return None
- debugger_arguments += get_debugger_info("args", [])
+ debugger_arguments += _get_debugger_info("args", [])
# Override the default debugger interactive mode if needed.
- debugger_interactive = get_debugger_info("interactive", False)
+ debugger_interactive = _get_debugger_info("interactive", False)
if debuggerInteractive:
debugger_interactive = debuggerInteractive
@@ -197,7 +208,7 @@ def get_debugger_info(debugger, debuggerArgs=None, debuggerInteractive=False):
debuggerPath,
debugger_interactive,
debugger_arguments,
- get_debugger_info("requiresEscapedArgs", False),
+ _get_debugger_info("requiresEscapedArgs", False),
)
return d
@@ -312,3 +323,28 @@ def get_default_valgrind_tool_specific_args():
"--show-possibly-lost=no",
"--show-mismatched-frees=no",
]
+
+
+def prepend_debugger_args(args, debugger, debugger_args):
+ """
+ Given an array with program arguments, prepend arguments to run it under a
+ debugger.
+
+ :param args: The executable and arguments used to run the process normally.
+ :param debugger: The debugger to use, or empty to use the default debugger.
+ :param debugger_args: Any additional parameters to pass to the debugger, as
+ a string using shell-like syntax.
+ """
+ if not debugger:
+ # No debugger name was provided. Look for the default ones on
+ # current OS.
+ debugger = get_default_debugger_name(DebuggerSearch.KeepLooking)
+
+ if debugger:
+ debuggerInfo = get_debugger_info(debugger, debugger_args)
+
+ if not debugger or not debuggerInfo:
+ print("Could not find a suitable debugger in your PATH.")
+ return None
+
+ return [debuggerInfo.path] + debuggerInfo.args + args
diff --git a/testing/talos/talos/talos_process.py b/testing/talos/talos/talos_process.py
@@ -11,6 +11,7 @@ from threading import Timer
import mozcrash
import psutil
+from mozdebug import prepend_debugger_args
from mozlog import get_proxy_logger
from mozscreenshot import dump_screen
@@ -133,10 +134,12 @@ def run_browser(
Returns a ProcessContext instance, with available output and pid used.
"""
- debugger_info = find_debugger_info(debug, debugger, debugger_args)
- if debugger_info is not None:
+ if debug or debugger or debugger_args:
+ command_under_dbg = prepend_debugger_args(command, debugger, debugger_args)
+ if not command_under_dbg:
+ raise TalosError("Could not find a suitable debugger in your PATH.")
return run_in_debug_mode(
- command, debugger_info, on_started=on_started, env=kwargs.get("env")
+ command_under_dbg, on_started=on_started, env=kwargs.get("env")
)
is_launcher = sys.platform.startswith("win") and "-wait-for-browser" in command
@@ -227,32 +230,9 @@ def run_browser(
return context
-def find_debugger_info(debug, debugger, debugger_args):
- debuggerInfo = None
- if debug or debugger or debugger_args:
- import mozdebug
-
- if not debugger:
- # No debugger name was provided. Look for the default ones on
- # current OS.
- debugger = mozdebug.get_default_debugger_name(
- mozdebug.DebuggerSearch.KeepLooking
- )
-
- debuggerInfo = None
- if debugger:
- debuggerInfo = mozdebug.get_debugger_info(debugger, debugger_args)
-
- if debuggerInfo is None:
- raise TalosError("Could not find a suitable debugger in your PATH.")
-
- return debuggerInfo
-
-
-def run_in_debug_mode(command, debugger_info, on_started=None, env=None):
+def run_in_debug_mode(command_under_dbg, on_started=None, env=None):
signal.signal(signal.SIGINT, lambda sigid, frame: None)
context = ProcessContext()
- command_under_dbg = [debugger_info.path] + debugger_info.args + command
ttest_process = subprocess.Popen(command_under_dbg, env=env)