blob: 7d569860ec29eb8368d920a4994b910349d54224 [file] [log] [blame]
Julius Werner634ccac2014-06-24 13:59:18 -07001# Copyright 2014 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Connect to a DUT in firmware via remote GDB, install custom GDB commands."""
6
Mike Frysinger383367e2014-09-16 15:06:17 -04007from __future__ import print_function
8
Julius Werner634ccac2014-06-24 13:59:18 -07009import errno
Julius Werner634ccac2014-06-24 13:59:18 -070010import os
11import re
12import signal
13import socket
14import time
15
16from chromite.cbuildbot import constants
17from chromite.lib import commandline
18from chromite.lib import cros_build_lib
Ralph Nathan91874ca2015-03-19 13:29:41 -070019from chromite.lib import cros_logging as logging
Julius Werner2c46b982015-02-13 12:47:20 -080020from chromite.lib import osutils
Julius Werner634ccac2014-06-24 13:59:18 -070021from chromite.lib import timeout_util
22
Julius Werner634ccac2014-06-24 13:59:18 -070023# Need to do this before Servo import
24cros_build_lib.AssertInsideChroot()
25
Mike Frysinger61ef29a2014-12-17 02:05:27 -050026# pylint: disable=import-error
Julius Werner634ccac2014-06-24 13:59:18 -070027from servo import client
28from servo import multiservo
Mike Frysinger61ef29a2014-12-17 02:05:27 -050029# pylint: enable=import-error
Julius Werner634ccac2014-06-24 13:59:18 -070030
Ralph Nathan91874ca2015-03-19 13:29:41 -070031
Julius Werner634ccac2014-06-24 13:59:18 -070032_SRC_ROOT = os.path.join(constants.CHROOT_SOURCE_ROOT, 'src')
33_SRC_DC = os.path.join(_SRC_ROOT, 'platform/depthcharge')
34_SRC_VB = os.path.join(_SRC_ROOT, 'platform/vboot_reference')
35_SRC_LP = os.path.join(_SRC_ROOT, 'third_party/coreboot/payloads/libpayload')
36
37_PTRN_DEVMODE = 'Entering VbBootDeveloper()'
38_PTRN_GDB = 'Ready for GDB connection'
Julius Werner20dfc992014-07-21 18:40:11 -070039_PTRN_BOARD = 'Starting(?: read-only| read/write)? depthcharge on ([a-z_]+)...'
Julius Werner634ccac2014-06-24 13:59:18 -070040
41
42class TerminalFreezer(object):
43 """SIGSTOP all processes (and their parents) that have the TTY open."""
44
45 def __init__(self, tty):
46 self._tty = tty
47 self._processes = None
Julius Werner2c46b982015-02-13 12:47:20 -080048 if 'cros_sdk' in osutils.ReadFile('/proc/1/cmdline'):
49 raise OSError('You must run this tool in a chroot that was entered with '
50 '"cros_sdk --no-ns-pid" (see crbug.com/444931 for details)')
Julius Werner634ccac2014-06-24 13:59:18 -070051
52 def __enter__(self):
Mike Frysingerd6e2df02014-11-26 02:55:04 -050053 lsof = cros_build_lib.RunCommand(
54 ['lsof', '-FR', self._tty],
Julius Werner634ccac2014-06-24 13:59:18 -070055 capture_output=True, log_output=True, error_code_ok=True)
56 self._processes = re.findall(r'^(?:R|p)(\d+)$', lsof.output, re.MULTILINE)
57
58 # SIGSTOP parents before children
59 try:
60 for p in reversed(self._processes):
Mike Frysinger2403af42015-04-30 06:25:03 -040061 logging.info('Sending SIGSTOP to process %s!', p)
Julius Werner634ccac2014-06-24 13:59:18 -070062 time.sleep(0.02)
63 os.kill(int(p), signal.SIGSTOP)
64 except OSError:
65 self.__exit__(None, None, None)
66 raise
67
68 def __exit__(self, _t, _v, _b):
69 # ...and wake 'em up again in reverse order
70 for p in self._processes:
Mike Frysinger2403af42015-04-30 06:25:03 -040071 logging.info('Sending SIGCONT to process %s!', p)
Julius Werner634ccac2014-06-24 13:59:18 -070072 try:
73 os.kill(int(p), signal.SIGCONT)
74 except OSError as e:
Mike Frysinger2403af42015-04-30 06:25:03 -040075 logging.error('Error when trying to unfreeze process %s: %s', p, e)
Julius Werner634ccac2014-06-24 13:59:18 -070076
77
78def ParsePortage(board):
79 """Parse some data from portage files. equery takes ages in comparison."""
80 with open(os.path.join('/build', board, 'packages/Packages'), 'r') as f:
81 chost = None
82 use = None
83 for line in f:
84 if line[:7] == 'CHOST: ':
85 chost = line[7:].strip()
86 if line[:5] == 'USE: ':
87 use = line[5:].strip()
88 if chost and use:
89 return (chost, use)
90
91
92def ParseArgs(argv):
93 """Parse and validate command line arguments."""
94 parser = commandline.ArgumentParser(default_log_level='warning')
95
96 parser.add_argument('-b', '--board',
97 help='The board overlay name (auto-detect by default)')
98 parser.add_argument('-s', '--symbols',
99 help='Root directory or complete path to symbolized ELF '
100 '(defaults to /build/<BOARD>/firmware)')
101 parser.add_argument('-r', '--reboot', choices=['yes', 'no', 'auto'],
102 help='Reboot the DUT before connect (default: reboot if '
103 'the remote and is unreachable)', default='auto')
104 parser.add_argument('-e', '--execute', action='append', default=[],
105 help='GDB command to run after connect (can be supplied '
106 'multiple times)')
107
108 parser.add_argument('-n', '--servod-name', dest='name')
109 parser.add_argument('--servod-rcfile', default=multiservo.DEFAULT_RC_FILE)
110 parser.add_argument('--servod-server')
111 parser.add_argument('-p', '--servod-port', type=int, dest='port')
112 parser.add_argument('-t', '--tty',
113 help='TTY file to connect to (defaults to cpu_uart_pty)')
114
115 opts = parser.parse_args(argv)
116 multiservo.get_env_options(logging, opts)
117 if opts.name:
118 rc = multiservo.parse_rc(logging, opts.servod_rcfile)
119 if opts.name not in rc:
120 raise parser.error('%s not in %s' % (opts.name, opts.servod_rcfile))
121 if not opts.servod_server:
122 opts.servod_server = rc[opts.name]['sn']
123 if not opts.port:
124 opts.port = rc[opts.name].get('port', client.DEFAULT_PORT)
125 if not opts.board and 'board' in rc[opts.name]:
126 opts.board = rc[opts.name]['board']
Mike Frysinger2403af42015-04-30 06:25:03 -0400127 logging.warn('Inferring board %s from %s; make sure this is correct!',
128 opts.board, opts.servod_rcfile)
Julius Werner634ccac2014-06-24 13:59:18 -0700129
130 if not opts.servod_server:
131 opts.servod_server = client.DEFAULT_HOST
132 if not opts.port:
133 opts.port = client.DEFAULT_PORT
134
135 return opts
136
137
138def FindSymbols(firmware_dir, board, use):
139 """Find the symbolized depthcharge ELF (may be supplied by -s flag)."""
140 if not firmware_dir:
141 firmware_dir = os.path.join(cros_build_lib.GetSysroot(board), 'firmware')
142 # Allow overriding the file directly just in case our detection screws up
143 if firmware_dir.endswith('.elf'):
144 return firmware_dir
145
146 if 'unified_depthcharge' in use:
Julius Werner20dfc992014-07-21 18:40:11 -0700147 basename = 'dev.elf'
Julius Werner634ccac2014-06-24 13:59:18 -0700148 else:
Julius Werner20dfc992014-07-21 18:40:11 -0700149 basename = 'dev.ro.elf'
Julius Werner634ccac2014-06-24 13:59:18 -0700150
Julius Werner20dfc992014-07-21 18:40:11 -0700151 path = os.path.join(firmware_dir, 'depthcharge', basename)
Julius Werner634ccac2014-06-24 13:59:18 -0700152 if not os.path.exists(path):
153 path = os.path.join(firmware_dir, basename)
154
155 if os.path.exists(path):
Mike Frysinger2403af42015-04-30 06:25:03 -0400156 logging.warn('Auto-detected symbol file at %s... make sure that this '
157 'matches the image on your DUT!', path)
Julius Werner634ccac2014-06-24 13:59:18 -0700158 return path
159
160 raise ValueError('Could not find %s symbol file!' % basename)
161
162
163# TODO(jwerner): Fine tune |wait| delay or maybe even make it configurable if
164# this causes problems due to load on the host. The callers where this is
165# critical should all have their own timeouts now, though, so it's questionable
166# whether the delay here is even needed at all anymore.
167def ReadAll(fd, wait=0.03):
168 """Read from |fd| until no more data has come for at least |wait| seconds."""
169 data = ''
170 try:
171 while True:
172 time.sleep(wait)
173 data += os.read(fd, 4096)
174 except OSError as e:
175 if e.errno == errno.EAGAIN:
Mike Frysinger2403af42015-04-30 06:25:03 -0400176 logging.debug(data)
Julius Werner634ccac2014-06-24 13:59:18 -0700177 return data
178 raise
179
180
181def GdbChecksum(message):
182 """Calculate a remote-GDB style checksum."""
183 chksum = sum([ord(x) for x in message])
184 return ('%.2x' % chksum)[-2:]
185
186
187def TestConnection(fd):
188 """Return True iff there is a resposive GDB stub on the other end of 'fd'."""
189 cmd = 'vUnknownCommand'
190 for _ in xrange(3):
191 os.write(fd, '$%s#%s\n' % (cmd, GdbChecksum(cmd)))
192 reply = ReadAll(fd)
193 if '+$#00' in reply:
194 os.write(fd, '+')
Mike Frysinger2403af42015-04-30 06:25:03 -0400195 logging.info('TestConnection: Could successfully connect to remote end.')
Julius Werner634ccac2014-06-24 13:59:18 -0700196 return True
Mike Frysinger2403af42015-04-30 06:25:03 -0400197 logging.info('TestConnection: Remote end does not respond.')
Julius Werner634ccac2014-06-24 13:59:18 -0700198 return False
199
200
201def main(argv):
202 opts = ParseArgs(argv)
203 servo = client.ServoClient(host=opts.servod_server, port=opts.port)
204
205 if not opts.tty:
206 try:
207 opts.tty = servo.get('cpu_uart_pty')
208 except (client.ServoClientError, socket.error):
Mike Frysinger2403af42015-04-30 06:25:03 -0400209 logging.error('Cannot auto-detect TTY file without servod. Use the --tty '
210 'option.')
Julius Werner634ccac2014-06-24 13:59:18 -0700211 raise
212 with TerminalFreezer(opts.tty):
213 fd = os.open(opts.tty, os.O_RDWR | os.O_NONBLOCK)
214
215 data = ReadAll(fd)
216 if opts.reboot == 'auto':
217 if TestConnection(fd):
218 opts.reboot = 'no'
219 else:
220 opts.reboot = 'yes'
221
222 if opts.reboot == 'yes':
Mike Frysinger2403af42015-04-30 06:25:03 -0400223 logging.info('Rebooting DUT...')
Julius Werner634ccac2014-06-24 13:59:18 -0700224 try:
225 servo.set('warm_reset', 'on')
226 time.sleep(0.1)
227 servo.set('warm_reset', 'off')
228 except (client.ServoClientError, socket.error):
Mike Frysinger2403af42015-04-30 06:25:03 -0400229 logging.error('Cannot reboot without a Servo board. You have to boot '
230 'into developer mode and press CTRL+G manually before '
231 'running fwgdb.')
Julius Werner634ccac2014-06-24 13:59:18 -0700232 raise
233
234 # Throw away old data to avoid confusion from messages before the reboot
235 data = ''
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500236 msg = ('Could not reboot into developer mode! '
237 '(Confirm that you have GBB_FLAG_FORCE_DEV_SWITCH_ON (0x8) set.)')
238 with timeout_util.Timeout(10, msg):
Julius Werner634ccac2014-06-24 13:59:18 -0700239 while _PTRN_DEVMODE not in data:
240 data += ReadAll(fd)
241
242 # Send a CTRL+G
Mike Frysinger2403af42015-04-30 06:25:03 -0400243 logging.info('Developer mode detected, pressing CTRL+G...')
Julius Werner634ccac2014-06-24 13:59:18 -0700244 os.write(fd, chr(ord('G') & 0x1f))
245
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500246 msg = ('Could not enter GDB mode with CTRL+G! '
247 '(Confirm that you flashed an "image.dev.bin" image to this DUT.)')
248 with timeout_util.Timeout(1, msg):
Julius Werner634ccac2014-06-24 13:59:18 -0700249 while _PTRN_GDB not in data:
250 data += ReadAll(fd)
251
252 if not opts.board:
253 matches = re.findall(_PTRN_BOARD, data)
254 if not matches:
255 raise ValueError('Could not auto-detect board! Please use -b option.')
256 opts.board = matches[-1]
Mike Frysinger2403af42015-04-30 06:25:03 -0400257 logging.info('Auto-detected board as %s from DUT console output.',
258 opts.board)
Julius Werner634ccac2014-06-24 13:59:18 -0700259
260 if not TestConnection(fd):
261 raise IOError('Could not connect to remote end! Confirm that your DUT is '
262 'running in GDB mode on %s.' % opts.tty)
263
264 # Eat up leftover data or it will spill back to terminal
265 ReadAll(fd)
266 os.close(fd)
267
268 opts.execute.insert(0, 'target remote %s' % opts.tty)
269 ex_args = sum([['--ex', cmd] for cmd in opts.execute], [])
270
271 chost, use = ParsePortage(opts.board)
Mike Frysinger2403af42015-04-30 06:25:03 -0400272 logging.info('Launching GDB...')
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500273 cros_build_lib.RunCommand(
274 [chost + '-gdb',
275 '--symbols', FindSymbols(opts.symbols, opts.board, use),
276 '--directory', _SRC_DC,
277 '--directory', _SRC_VB,
278 '--directory', _SRC_LP] + ex_args,
Julius Werner634ccac2014-06-24 13:59:18 -0700279 ignore_sigint=True, debug_level=logging.WARNING)