| # 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() |