tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mpython/mozbuild/mozbuild/mach_commands.py | 103+++++++------------------------------------------------------------------------
Mtesting/geckodriver/mach_commands.py | 35++++-------------------------------
Mtesting/mozbase/mozdebug/mozdebug/__init__.py | 2+-
Mtesting/mozbase/mozdebug/mozdebug/mozdebug.py | 54+++++++++++++++++++++++++++++++++++++++++++++---------
Mtesting/talos/talos/talos_process.py | 34+++++++---------------------------
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)