| # Copyright 2015 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """cros debug: Debug the applications on the target device.""" |
| |
| import logging |
| import os |
| |
| from chromite.cli import command |
| from chromite.lib import commandline |
| from chromite.lib import cros_build_lib |
| from chromite.lib import remote_access |
| |
| |
| @command.command_decorator("debug") |
| class DebugCommand(command.CliCommand): |
| """Use GDB to debug a process running on the target device. |
| |
| This command starts a GDB session to debug a remote process running on the |
| target device. The remote process can either be an existing process or newly |
| started by calling this command. |
| |
| This command can also be used to find out information about all running |
| processes of an executable on the target device. |
| """ |
| |
| EPILOG = """ |
| To list all running processes of an executable: |
| cros debug device --list --exe=/path/to/executable |
| |
| To debug an executable: |
| cros debug device --exe=/path/to/executable |
| |
| To debug a process by its pid: |
| cros debug device --pid=1234 |
| """ |
| |
| def __init__(self, options): |
| """Initialize DebugCommand.""" |
| super().__init__(options) |
| # SSH connection settings. |
| self.ssh_hostname = None |
| self.ssh_port = None |
| self.ssh_username = None |
| self.ssh_private_key = None |
| # The board name of the target device. |
| self.board = None |
| # Settings of the process to debug. |
| self.list = False |
| self.exe = None |
| self.pid = None |
| # The command for starting gdb. |
| self.gdb_cmd = None |
| |
| @classmethod |
| def AddParser(cls, parser): |
| """Add parser arguments.""" |
| super(cls, DebugCommand).AddParser(parser) |
| cls.AddDeviceArgument(parser, positional=True) |
| parser.add_argument( |
| "--board", |
| default=None, |
| help="The board to use. By default it is " |
| "automatically detected. You can override the detected board with " |
| "this option.", |
| ) |
| parser.add_argument( |
| "--private-key", |
| type="path", |
| default=None, |
| help="SSH identity file (private key).", |
| ) |
| parser.add_argument( |
| "-l", |
| "--list", |
| action="store_true", |
| default=False, |
| help="List running processes of the executable on the target " |
| "device.", |
| ) |
| parser.add_argument( |
| "--exe", help="Full path of the executable on the target device." |
| ) |
| parser.add_argument( |
| "-p", |
| "--pid", |
| type=int, |
| help="The pid of the process on the target device.", |
| ) |
| |
| @classmethod |
| def ProcessOptions(cls, parser, options): |
| """Post process options.""" |
| if not (options.pid or options.exe): |
| parser.error( |
| "Must use --exe or --pid to specify the process to debug." |
| ) |
| |
| if options.pid and (options.list or options.exe): |
| parser.error("--list and --exe are disallowed when --pid is used.") |
| |
| if not options.exe.startswith("/"): |
| parser.error("--exe must have a full pathname.") |
| |
| def _ListProcesses(self, device, pids): |
| """Print out information of the processes in |pids|.""" |
| if not pids: |
| logging.info( |
| "No running process of %s on device %s", |
| self.exe, |
| self.ssh_hostname, |
| ) |
| return |
| |
| try: |
| result = device.run(["ps", "aux"]) |
| lines = result.stdout.splitlines() |
| try: |
| header, procs = lines[0], lines[1:] |
| info = os.linesep.join( |
| [p for p in procs if int(p.split()[1]) in pids] |
| ) |
| except ValueError: |
| cros_build_lib.Die("Parsing output failed:\n%s", result.stdout) |
| |
| print( |
| "\nList running processes of %s on device %s:\n%s\n%s" |
| % (self.exe, self.ssh_hostname, header, info) |
| ) |
| except cros_build_lib.RunCommandError: |
| cros_build_lib.Die( |
| "Failed to find any running process on device %s", |
| self.ssh_hostname, |
| ) |
| |
| def _DebugNewProcess(self): |
| """Start a new process on the target device and attach gdb to it.""" |
| logging.info( |
| "Ready to start and debug %s on device %s", |
| self.exe, |
| self.ssh_hostname, |
| ) |
| cros_build_lib.run(self.gdb_cmd + ["--remote_file", self.exe]) |
| |
| def _DebugRunningProcess(self, pid): |
| """Start gdb and attach it to the remote running process with |pid|.""" |
| logging.info( |
| "Ready to debug process %d on device %s", pid, self.ssh_hostname |
| ) |
| cros_build_lib.run(self.gdb_cmd + ["--pid", str(pid)]) |
| |
| def _ReadOptions(self): |
| """Process options and set variables.""" |
| if self.options.device: |
| self.ssh_hostname = self.options.device.hostname |
| self.ssh_username = self.options.device.username |
| self.ssh_port = self.options.device.port |
| self.ssh_private_key = self.options.private_key |
| self.list = self.options.list |
| self.exe = self.options.exe |
| self.pid = self.options.pid |
| |
| def Run(self): |
| """Run cros debug.""" |
| commandline.RunInsideChroot(self) |
| self._ReadOptions() |
| with remote_access.ChromiumOSDeviceHandler( |
| self.ssh_hostname, |
| port=self.ssh_port, |
| username=self.ssh_username, |
| private_key=self.ssh_private_key, |
| ) as device: |
| self.board = cros_build_lib.GetBoard( |
| device_board=device.board, |
| override_board=self.options.board, |
| strict=True, |
| ) |
| logging.info("Board is %s", self.board) |
| |
| self.gdb_cmd = [ |
| "gdb_remote", |
| "--ssh", |
| "--board", |
| self.board, |
| "--remote", |
| self.ssh_hostname, |
| ] |
| if self.ssh_port: |
| self.gdb_cmd.extend(["--ssh_port", str(self.ssh_port)]) |
| |
| if self.pid: |
| self._DebugRunningProcess(self.pid) |
| return |
| |
| logging.debug("Executable path is %s", self.exe) |
| if not device.IsFileExecutable(self.exe): |
| cros_build_lib.Die( |
| 'File path "%s" does not exist or is not executable on ' |
| "device %s", |
| self.exe, |
| self.ssh_hostname, |
| ) |
| |
| pids = device.GetRunningPids(self.exe) |
| self._ListProcesses(device, pids) |
| |
| if self.list: |
| # If '--list' flag is on, do not launch GDB. |
| return |
| |
| if pids: |
| choices = ["Start a new process under GDB"] |
| choices.extend(pids) |
| idx = cros_build_lib.GetChoice( |
| "Please select the process pid to debug (select [0] to " |
| "start a new process):", |
| choices, |
| ) |
| if idx == 0: |
| self._DebugNewProcess() |
| else: |
| self._DebugRunningProcess(pids[idx - 1]) |
| else: |
| self._DebugNewProcess() |