• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2023 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5from __future__ import annotations
6
7import shlex
8from typing import TYPE_CHECKING, Dict, Iterable
9
10from crossbench import plt
11from crossbench.browsers.attributes import BrowserAttributes
12from crossbench.parse import PathParser
13from crossbench.probes.probe import (Probe, ProbeConfigParser, ProbeContext,
14                                     ProbeKeyT, ProbeValidationError)
15from crossbench.probes.result_location import ResultLocation
16from crossbench.probes.results import EmptyProbeResult, ProbeResult
17
18if TYPE_CHECKING:
19  from crossbench.browsers.browser import Browser
20  from crossbench.env import HostEnvironment
21  from crossbench.path import LocalPath
22  from crossbench.runner.run import Run
23
24_DEBUGGER_LOOKUP: Dict[str, str] = {
25    "macos": "lldb",
26    "linux": "gdb",
27}
28
29DEFAULT_GEOMETRY = "80x70"
30
31
32class DebuggerProbe(Probe):
33  """
34  Probe debugging chrome's renderer process.
35  """
36  NAME = "debugger"
37  RESULT_LOCATION = ResultLocation.BROWSER
38  IS_GENERAL_PURPOSE = True
39
40  @classmethod
41  def config_parser(cls) -> ProbeConfigParser:
42    parser = super().config_parser()
43    parser.add_argument(
44        "debugger",
45        type=PathParser.binary_path,
46        default=_DEBUGGER_LOOKUP.get(plt.PLATFORM.name,
47                                     "debugger probe not supported"),
48        help="Set a custom debugger binary. "
49        "Currently only gdb and lldb are supported.")
50    parser.add_argument(
51        "auto_run",
52        type=bool,
53        default=True,
54        help="Automatically start the renderer process in the debugger.")
55    parser.add_argument(
56        "spare_renderer_process",
57        type=bool,
58        default=False,
59        help=("Chrome-only: Enable/Disable spare renderer processes via \n"
60              "--enable-/--disable-features=SpareRendererForSitePerProcess.\n"
61              "Spare renderers are disabled by default when profiling "
62              "for fewer uninteresting processes."))
63    parser.add_argument(
64        "geometry",
65        type=str,
66        default=DEFAULT_GEOMETRY,
67        help="Geometry of the terminal (xterm) used to display the debugger.")
68    parser.add_argument(
69        "args",
70        type=str,
71        default=tuple(),
72        is_list=True,
73        help="Additional args that are passed to the debugger.")
74    return parser
75
76  def __init__(
77      self,
78      debugger: LocalPath,
79      auto_run: bool = True,
80      spare_renderer_process: bool = False,
81      geometry: str = DEFAULT_GEOMETRY,
82      args: Iterable[str] = ()) -> None:
83    super().__init__()
84    self._debugger_bin = debugger
85    self._debugger_args = args
86    self._auto_run = auto_run
87    self._geometry = geometry
88    self._spare_renderer_process = spare_renderer_process
89
90  @property
91  def key(self) -> ProbeKeyT:
92    return super().key + (
93        ("debugger", str(self._debugger_bin)),
94        ("debugger_args", tuple(self._debugger_args)),
95        ("auto_run", self._auto_run),
96        ("geometry", str(self._geometry)),
97        ("spare_renderer_process", self._spare_renderer_process),
98    )
99
100  def validate_browser(self, env: HostEnvironment, browser: Browser) -> None:
101    super().validate_browser(env, browser)
102    self.expect_browser(browser, BrowserAttributes.CHROMIUM_BASED)
103    # TODO: support more platforms
104    if not (browser.platform.is_macos or browser.platform.is_linux):
105      raise ValueError(f"Only supported on linux and macOS, but got {browser}")
106    if browser.platform.is_remote:
107      raise ProbeValidationError(self, "Does not run on remote platforms.")
108    # TODO: support more terminals.
109    if not browser.platform.which("xterm"):
110      raise ProbeValidationError(self, "Please install xterm on your system.")
111
112  def attach(self, browser: Browser) -> None:
113    super().attach(browser)
114    assert browser.attributes.is_chromium_based
115    flags = browser.flags
116    flags.set("--no-sandbox")
117    flags.set("--disable-hang-monitor")
118    flags["--renderer-cmd-prefix"] = self.renderer_cmd_prefix()
119    if not self._spare_renderer_process:
120      browser.features.disable("SpareRendererForSitePerProcess")
121
122  def renderer_cmd_prefix(self) -> str:
123    # TODO: support more terminals.
124    debugger_cmd = [
125        "xterm",
126        "-title",
127        "renderer",
128        "-geometry",
129        self._geometry,
130        "-e",
131        str(self._debugger_bin),
132    ]
133    if self._debugger_bin.name == "lldb":
134      if self._auto_run:
135        debugger_cmd += ["-o", "run"]
136      if self._debugger_args:
137        debugger_cmd.extend(self._debugger_args)
138      debugger_cmd += ["--"]
139    else:
140      assert self._debugger_bin.name == "gdb", (
141          f"Unsupported debugger: {self._debugger_bin}")
142      if self._auto_run:
143        debugger_cmd += ["-ex", "run"]
144      if self._debugger_args:
145        debugger_cmd.extend(self._debugger_args)
146      debugger_cmd += ["--args"]
147    return shlex.join(debugger_cmd)
148
149  def get_context(self, run: Run) -> DebuggerContext:
150    return DebuggerContext(self, run)
151
152
153class DebuggerContext(ProbeContext[DebuggerProbe]):
154
155  def start(self) -> None:
156    pass
157
158  def stop(self) -> None:
159    pass
160
161  def teardown(self) -> ProbeResult:
162    return EmptyProbeResult()
163