blob: e7d0063e63791fd789715be01ee2f39b0cd1b09b [file] [log] [blame]
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from __future__ import annotations
import shlex
from typing import TYPE_CHECKING, Dict, Iterable
from crossbench import plt
from crossbench.browsers.attributes import BrowserAttributes
from crossbench.parse import PathParser
from crossbench.probes.probe import (Probe, ProbeConfigParser, ProbeContext,
ProbeKeyT, ProbeValidationError)
from crossbench.probes.result_location import ResultLocation
from crossbench.probes.results import EmptyProbeResult, ProbeResult
if TYPE_CHECKING:
from crossbench.browsers.browser import Browser
from crossbench.env import HostEnvironment
from crossbench.path import LocalPath
from crossbench.runner.run import Run
_DEBUGGER_LOOKUP: Dict[str, str] = {
"macos": "lldb",
"linux": "gdb",
}
DEFAULT_GEOMETRY = "80x70"
class DebuggerProbe(Probe):
"""
Probe debugging chrome's renderer process.
"""
NAME = "debugger"
RESULT_LOCATION = ResultLocation.BROWSER
IS_GENERAL_PURPOSE = True
@classmethod
def config_parser(cls) -> ProbeConfigParser:
parser = super().config_parser()
parser.add_argument(
"debugger",
type=PathParser.binary_path,
default=_DEBUGGER_LOOKUP.get(plt.PLATFORM.name,
"debugger probe not supported"),
help="Set a custom debugger binary. "
"Currently only gdb and lldb are supported.")
parser.add_argument(
"auto_run",
type=bool,
default=True,
help="Automatically start the renderer process in the debugger.")
parser.add_argument(
"spare_renderer_process",
type=bool,
default=False,
help=("Chrome-only: Enable/Disable spare renderer processes via \n"
"--enable-/--disable-features=SpareRendererForSitePerProcess.\n"
"Spare renderers are disabled by default when profiling "
"for fewer uninteresting processes."))
parser.add_argument(
"geometry",
type=str,
default=DEFAULT_GEOMETRY,
help="Geometry of the terminal (xterm) used to display the debugger.")
parser.add_argument(
"args",
type=str,
default=tuple(),
is_list=True,
help="Additional args that are passed to the debugger.")
return parser
def __init__(
self,
debugger: LocalPath,
auto_run: bool = True,
spare_renderer_process: bool = False,
geometry: str = DEFAULT_GEOMETRY,
args: Iterable[str] = ()) -> None:
super().__init__()
self._debugger_bin = debugger
self._debugger_args = args
self._auto_run = auto_run
self._geometry = geometry
self._spare_renderer_process = spare_renderer_process
@property
def key(self) -> ProbeKeyT:
return super().key + (
("debugger", str(self._debugger_bin)),
("debugger_args", tuple(self._debugger_args)),
("auto_run", self._auto_run),
("geometry", str(self._geometry)),
("spare_renderer_process", self._spare_renderer_process),
)
def validate_browser(self, env: HostEnvironment, browser: Browser) -> None:
super().validate_browser(env, browser)
self.expect_browser(browser, BrowserAttributes.CHROMIUM_BASED)
# TODO: support more platforms
if not (browser.platform.is_macos or browser.platform.is_linux):
raise ValueError(f"Only supported on linux and macOS, but got {browser}")
if browser.platform.is_remote:
raise ProbeValidationError(self, "Does not run on remote platforms.")
# TODO: support more terminals.
if not browser.platform.which("xterm"):
raise ProbeValidationError(self, "Please install xterm on your system.")
def attach(self, browser: Browser) -> None:
super().attach(browser)
assert browser.attributes.is_chromium_based
flags = browser.flags
flags.set("--no-sandbox")
flags.set("--disable-hang-monitor")
flags["--renderer-cmd-prefix"] = self.renderer_cmd_prefix()
if not self._spare_renderer_process:
browser.features.disable("SpareRendererForSitePerProcess")
def renderer_cmd_prefix(self) -> str:
# TODO: support more terminals.
debugger_cmd = [
"xterm",
"-title",
"renderer",
"-geometry",
self._geometry,
"-e",
str(self._debugger_bin),
]
if self._debugger_bin.name == "lldb":
if self._auto_run:
debugger_cmd += ["-o", "run"]
if self._debugger_args:
debugger_cmd.extend(self._debugger_args)
debugger_cmd += ["--"]
else:
assert self._debugger_bin.name == "gdb", (
f"Unsupported debugger: {self._debugger_bin}")
if self._auto_run:
debugger_cmd += ["-ex", "run"]
if self._debugger_args:
debugger_cmd.extend(self._debugger_args)
debugger_cmd += ["--args"]
return shlex.join(debugger_cmd)
def get_context(self, run: Run) -> DebuggerContext:
return DebuggerContext(self, run)
class DebuggerContext(ProbeContext[DebuggerProbe]):
def start(self) -> None:
pass
def stop(self) -> None:
pass
def teardown(self) -> ProbeResult:
return EmptyProbeResult()