blob: 7265d85a7dcd5bce489d0724379421e4b57c92b1 [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
Julius Werner634ccac2014-06-24 13:59:18 -070012import socket
13import time
14
Aviv Keshetb7519e12016-10-04 00:50:00 -070015from chromite.lib import constants
Julius Werner634ccac2014-06-24 13:59:18 -070016from chromite.lib import commandline
17from chromite.lib import cros_build_lib
Ralph Nathan91874ca2015-03-19 13:29:41 -070018from chromite.lib import cros_logging as logging
Julius Werner634ccac2014-06-24 13:59:18 -070019from chromite.lib import timeout_util
20
Julius Werner634ccac2014-06-24 13:59:18 -070021# Need to do this before Servo import
22cros_build_lib.AssertInsideChroot()
23
Mike Frysinger61ef29a2014-12-17 02:05:27 -050024# pylint: disable=import-error
Julius Werner634ccac2014-06-24 13:59:18 -070025from servo import client
26from servo import multiservo
Aseda Aboagye6e21d3f2016-10-21 11:37:56 -070027from servo import terminal_freezer
Mike Frysinger61ef29a2014-12-17 02:05:27 -050028# pylint: enable=import-error
Julius Werner634ccac2014-06-24 13:59:18 -070029
Ralph Nathan91874ca2015-03-19 13:29:41 -070030
Julius Werner634ccac2014-06-24 13:59:18 -070031_SRC_ROOT = os.path.join(constants.CHROOT_SOURCE_ROOT, 'src')
32_SRC_DC = os.path.join(_SRC_ROOT, 'platform/depthcharge')
33_SRC_VB = os.path.join(_SRC_ROOT, 'platform/vboot_reference')
34_SRC_LP = os.path.join(_SRC_ROOT, 'third_party/coreboot/payloads/libpayload')
35
36_PTRN_DEVMODE = 'Entering VbBootDeveloper()'
37_PTRN_GDB = 'Ready for GDB connection'
Julius Werner20dfc992014-07-21 18:40:11 -070038_PTRN_BOARD = 'Starting(?: read-only| read/write)? depthcharge on ([a-z_]+)...'
Julius Werner634ccac2014-06-24 13:59:18 -070039
40
Julius Werner634ccac2014-06-24 13:59:18 -070041def ParsePortage(board):
42 """Parse some data from portage files. equery takes ages in comparison."""
43 with open(os.path.join('/build', board, 'packages/Packages'), 'r') as f:
44 chost = None
45 use = None
46 for line in f:
47 if line[:7] == 'CHOST: ':
48 chost = line[7:].strip()
49 if line[:5] == 'USE: ':
50 use = line[5:].strip()
51 if chost and use:
52 return (chost, use)
53
54
55def ParseArgs(argv):
56 """Parse and validate command line arguments."""
57 parser = commandline.ArgumentParser(default_log_level='warning')
58
59 parser.add_argument('-b', '--board',
60 help='The board overlay name (auto-detect by default)')
61 parser.add_argument('-s', '--symbols',
62 help='Root directory or complete path to symbolized ELF '
63 '(defaults to /build/<BOARD>/firmware)')
64 parser.add_argument('-r', '--reboot', choices=['yes', 'no', 'auto'],
65 help='Reboot the DUT before connect (default: reboot if '
66 'the remote and is unreachable)', default='auto')
67 parser.add_argument('-e', '--execute', action='append', default=[],
68 help='GDB command to run after connect (can be supplied '
69 'multiple times)')
70
71 parser.add_argument('-n', '--servod-name', dest='name')
72 parser.add_argument('--servod-rcfile', default=multiservo.DEFAULT_RC_FILE)
73 parser.add_argument('--servod-server')
74 parser.add_argument('-p', '--servod-port', type=int, dest='port')
75 parser.add_argument('-t', '--tty',
76 help='TTY file to connect to (defaults to cpu_uart_pty)')
77
78 opts = parser.parse_args(argv)
79 multiservo.get_env_options(logging, opts)
80 if opts.name:
81 rc = multiservo.parse_rc(logging, opts.servod_rcfile)
82 if opts.name not in rc:
83 raise parser.error('%s not in %s' % (opts.name, opts.servod_rcfile))
84 if not opts.servod_server:
85 opts.servod_server = rc[opts.name]['sn']
86 if not opts.port:
87 opts.port = rc[opts.name].get('port', client.DEFAULT_PORT)
88 if not opts.board and 'board' in rc[opts.name]:
89 opts.board = rc[opts.name]['board']
Bertrand SIMONNET0ddf7762015-05-20 14:38:00 -070090 logging.warning('Inferring board %s from %s; make sure this is correct!',
91 opts.board, opts.servod_rcfile)
Julius Werner634ccac2014-06-24 13:59:18 -070092
93 if not opts.servod_server:
94 opts.servod_server = client.DEFAULT_HOST
95 if not opts.port:
96 opts.port = client.DEFAULT_PORT
97
98 return opts
99
100
101def FindSymbols(firmware_dir, board, use):
102 """Find the symbolized depthcharge ELF (may be supplied by -s flag)."""
103 if not firmware_dir:
104 firmware_dir = os.path.join(cros_build_lib.GetSysroot(board), 'firmware')
105 # Allow overriding the file directly just in case our detection screws up
106 if firmware_dir.endswith('.elf'):
107 return firmware_dir
108
109 if 'unified_depthcharge' in use:
Julius Werner20dfc992014-07-21 18:40:11 -0700110 basename = 'dev.elf'
Julius Werner634ccac2014-06-24 13:59:18 -0700111 else:
Julius Werner20dfc992014-07-21 18:40:11 -0700112 basename = 'dev.ro.elf'
Julius Werner634ccac2014-06-24 13:59:18 -0700113
Julius Werner20dfc992014-07-21 18:40:11 -0700114 path = os.path.join(firmware_dir, 'depthcharge', basename)
Julius Werner634ccac2014-06-24 13:59:18 -0700115 if not os.path.exists(path):
116 path = os.path.join(firmware_dir, basename)
117
118 if os.path.exists(path):
Bertrand SIMONNET0ddf7762015-05-20 14:38:00 -0700119 logging.warning('Auto-detected symbol file at %s... make sure that this '
120 'matches the image on your DUT!', path)
Julius Werner634ccac2014-06-24 13:59:18 -0700121 return path
122
123 raise ValueError('Could not find %s symbol file!' % basename)
124
125
126# TODO(jwerner): Fine tune |wait| delay or maybe even make it configurable if
127# this causes problems due to load on the host. The callers where this is
128# critical should all have their own timeouts now, though, so it's questionable
129# whether the delay here is even needed at all anymore.
130def ReadAll(fd, wait=0.03):
131 """Read from |fd| until no more data has come for at least |wait| seconds."""
132 data = ''
133 try:
134 while True:
135 time.sleep(wait)
136 data += os.read(fd, 4096)
137 except OSError as e:
138 if e.errno == errno.EAGAIN:
Mike Frysinger2403af42015-04-30 06:25:03 -0400139 logging.debug(data)
Julius Werner634ccac2014-06-24 13:59:18 -0700140 return data
141 raise
142
143
144def GdbChecksum(message):
145 """Calculate a remote-GDB style checksum."""
146 chksum = sum([ord(x) for x in message])
147 return ('%.2x' % chksum)[-2:]
148
149
150def TestConnection(fd):
151 """Return True iff there is a resposive GDB stub on the other end of 'fd'."""
152 cmd = 'vUnknownCommand'
153 for _ in xrange(3):
154 os.write(fd, '$%s#%s\n' % (cmd, GdbChecksum(cmd)))
155 reply = ReadAll(fd)
156 if '+$#00' in reply:
157 os.write(fd, '+')
Mike Frysinger2403af42015-04-30 06:25:03 -0400158 logging.info('TestConnection: Could successfully connect to remote end.')
Julius Werner634ccac2014-06-24 13:59:18 -0700159 return True
Mike Frysinger2403af42015-04-30 06:25:03 -0400160 logging.info('TestConnection: Remote end does not respond.')
Julius Werner634ccac2014-06-24 13:59:18 -0700161 return False
162
163
164def main(argv):
165 opts = ParseArgs(argv)
166 servo = client.ServoClient(host=opts.servod_server, port=opts.port)
167
168 if not opts.tty:
169 try:
170 opts.tty = servo.get('cpu_uart_pty')
171 except (client.ServoClientError, socket.error):
Mike Frysinger2403af42015-04-30 06:25:03 -0400172 logging.error('Cannot auto-detect TTY file without servod. Use the --tty '
173 'option.')
Julius Werner634ccac2014-06-24 13:59:18 -0700174 raise
Aseda Aboagye6e21d3f2016-10-21 11:37:56 -0700175 with terminal_freezer.TerminalFreezer(opts.tty):
Julius Werner634ccac2014-06-24 13:59:18 -0700176 fd = os.open(opts.tty, os.O_RDWR | os.O_NONBLOCK)
177
178 data = ReadAll(fd)
179 if opts.reboot == 'auto':
180 if TestConnection(fd):
181 opts.reboot = 'no'
182 else:
183 opts.reboot = 'yes'
184
185 if opts.reboot == 'yes':
Mike Frysinger2403af42015-04-30 06:25:03 -0400186 logging.info('Rebooting DUT...')
Julius Werner634ccac2014-06-24 13:59:18 -0700187 try:
188 servo.set('warm_reset', 'on')
189 time.sleep(0.1)
190 servo.set('warm_reset', 'off')
191 except (client.ServoClientError, socket.error):
Mike Frysinger2403af42015-04-30 06:25:03 -0400192 logging.error('Cannot reboot without a Servo board. You have to boot '
193 'into developer mode and press CTRL+G manually before '
194 'running fwgdb.')
Julius Werner634ccac2014-06-24 13:59:18 -0700195 raise
196
197 # Throw away old data to avoid confusion from messages before the reboot
198 data = ''
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500199 msg = ('Could not reboot into developer mode! '
200 '(Confirm that you have GBB_FLAG_FORCE_DEV_SWITCH_ON (0x8) set.)')
201 with timeout_util.Timeout(10, msg):
Julius Werner634ccac2014-06-24 13:59:18 -0700202 while _PTRN_DEVMODE not in data:
203 data += ReadAll(fd)
204
205 # Send a CTRL+G
Mike Frysinger2403af42015-04-30 06:25:03 -0400206 logging.info('Developer mode detected, pressing CTRL+G...')
Julius Werner634ccac2014-06-24 13:59:18 -0700207 os.write(fd, chr(ord('G') & 0x1f))
208
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500209 msg = ('Could not enter GDB mode with CTRL+G! '
210 '(Confirm that you flashed an "image.dev.bin" image to this DUT.)')
211 with timeout_util.Timeout(1, msg):
Julius Werner634ccac2014-06-24 13:59:18 -0700212 while _PTRN_GDB not in data:
213 data += ReadAll(fd)
214
215 if not opts.board:
216 matches = re.findall(_PTRN_BOARD, data)
217 if not matches:
218 raise ValueError('Could not auto-detect board! Please use -b option.')
219 opts.board = matches[-1]
Mike Frysinger2403af42015-04-30 06:25:03 -0400220 logging.info('Auto-detected board as %s from DUT console output.',
221 opts.board)
Julius Werner634ccac2014-06-24 13:59:18 -0700222
223 if not TestConnection(fd):
224 raise IOError('Could not connect to remote end! Confirm that your DUT is '
225 'running in GDB mode on %s.' % opts.tty)
226
227 # Eat up leftover data or it will spill back to terminal
228 ReadAll(fd)
229 os.close(fd)
230
231 opts.execute.insert(0, 'target remote %s' % opts.tty)
232 ex_args = sum([['--ex', cmd] for cmd in opts.execute], [])
233
234 chost, use = ParsePortage(opts.board)
Mike Frysinger2403af42015-04-30 06:25:03 -0400235 logging.info('Launching GDB...')
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500236 cros_build_lib.RunCommand(
237 [chost + '-gdb',
238 '--symbols', FindSymbols(opts.symbols, opts.board, use),
239 '--directory', _SRC_DC,
240 '--directory', _SRC_VB,
241 '--directory', _SRC_LP] + ex_args,
Julius Werner634ccac2014-06-24 13:59:18 -0700242 ignore_sigint=True, debug_level=logging.WARNING)