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