blob: 9d4329aef88929d2aa5f955b367f5fedf6cc028f [file] [log] [blame]
# Copyright 2015 The Chromium OS Authors. All rights reserved.
# 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."""
from __future__ import print_function
import os
import logging
import urlparse
from chromite import cros
from chromite.lib import cros_build_lib
from chromite.lib import remote_access
@cros.CommandDecorator('debug')
class DebugCommand(cros.CrosCommand):
"""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.
"""
# Override base class property to enable stats upload.
upload_stats = True
def __init__(self, options):
"""Initialize DebugCommand."""
cros.CrosCommand.__init__(self, 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.attach = False
self.list = False
self.exe = None
self.pid = None
@classmethod
def AddParser(cls, parser):
"""Add parser arguments."""
super(cls, DebugCommand).AddParser(parser)
parser.add_argument(
'device', help='IP[:port] address of the target device.')
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(
'--attach', action='store_true', default=False,
help='Attach GDB to an already running process on the target device.')
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.')
def _GetRunningPids(self, device):
"""Get all the running pids on the device with the executable path."""
try:
result = device.BaseRunCommand(['pgrep', '-fx', self.exe])
try:
return [int(pid) for pid in result.output.splitlines()]
except ValueError:
cros_build_lib.Die('Parsing output failed:\n%s', result.output)
except cros_build_lib.RunCommandError:
cros_build_lib.Die(
'Failed to find any running process of %s on device %s', self.exe,
self.ssh_hostname)
def _ListProcesses(self, device, pids):
"""Provided with a list of pids, print out information of the processes."""
try:
result = device.BaseRunCommand(['ps', 'aux'])
lines = result.output.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.output)
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)
cmd = [
'gdb_remote', '--ssh',
'--board', self.board,
'--remote', self.ssh_hostname,
'--remote_file', self.exe,
]
cros_build_lib.RunCommand(cmd)
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)
cmd = [
'gdb_remote', '--ssh',
'--board', self.board,
'--remote', self.ssh_hostname,
'--remote_pid', str(pid),
]
cros_build_lib.RunCommand(cmd)
def _ReadOptions(self):
"""Process options and set variables."""
device = self.options.device
if urlparse.urlparse(device).scheme == '':
# For backward compatibility, prepend ssh:// ourselves.
device = 'ssh://%s' % device
parsed = urlparse.urlparse(device)
if parsed.scheme == 'ssh':
self.ssh_hostname = parsed.hostname
self.ssh_username = parsed.username
self.ssh_port = parsed.port
self.ssh_private_key = self.options.private_key
self.attach = self.options.attach
self.list = self.options.list
self.exe = self.options.exe
self.pid = self.options.pid
else:
cros_build_lib.Die('Does not support device %s', self.options.device)
def Run(self):
"""Run cros debug."""
self.options.Freeze()
self._ReadOptions()
self.RunInsideChroot(auto_detect_project=True)
try:
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)
logging.info('Board is %s', self.board)
if self.pid:
# Ignore other flags and start GDB on the pid if it is provided.
self._DebugRunningProcess(self.pid)
return
if not (self.exe and self.exe.startswith('/')):
cros_build_lib.Die('--exe is required and must be a full pathname.')
logging.info('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)
if self.list:
# If '--list' flag is on, list the processes without launching GDB.
pids = self._GetRunningPids(device)
self._ListProcesses(device, pids)
return
if self.attach:
pids = self._GetRunningPids(device)
if not pids:
cros_build_lib.Die('No running process of %s is found on device %s',
self.exe, self.ssh_hostname)
elif len(pids) == 1:
idx = 0
else:
self._ListProcesses(device, pids)
idx = cros_build_lib.GetChoice(
'Please select the process pid to debug:', pids)
self._DebugRunningProcess(pids[idx])
return
self._DebugNewProcess()
except (Exception, KeyboardInterrupt) as e:
logging.error(e)
if self.options.debug:
raise