blob: f8d772d385b98530d45139eca884078a4cdc4b2c [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
Julius Werner634ccac2014-06-24 13:59:18 -07002# Copyright 2014 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Connect to a DUT in firmware via remote GDB, install custom GDB commands."""
7
Mike Frysinger383367e2014-09-16 15:06:17 -04008from __future__ import print_function
9
Julius Werner634ccac2014-06-24 13:59:18 -070010import errno
Raul E Rangeleb8aadc2018-05-17 09:10:27 -060011import glob
Julius Werner634ccac2014-06-24 13:59:18 -070012import os
13import re
Julius Werner634ccac2014-06-24 13:59:18 -070014import socket
15import time
16
Aviv Keshetb7519e12016-10-04 00:50:00 -070017from chromite.lib import constants
Julius Werner634ccac2014-06-24 13:59:18 -070018from chromite.lib import commandline
19from chromite.lib import cros_build_lib
Ralph Nathan91874ca2015-03-19 13:29:41 -070020from chromite.lib import cros_logging as logging
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
Aseda Aboagye6e21d3f2016-10-21 11:37:56 -070029from servo import terminal_freezer
Mike Frysinger61ef29a2014-12-17 02:05:27 -050030# pylint: enable=import-error
Julius Werner634ccac2014-06-24 13:59:18 -070031
Ralph Nathan91874ca2015-03-19 13:29:41 -070032
Julius Werner634ccac2014-06-24 13:59:18 -070033_SRC_ROOT = os.path.join(constants.CHROOT_SOURCE_ROOT, 'src')
34_SRC_DC = os.path.join(_SRC_ROOT, 'platform/depthcharge')
35_SRC_VB = os.path.join(_SRC_ROOT, 'platform/vboot_reference')
36_SRC_LP = os.path.join(_SRC_ROOT, 'third_party/coreboot/payloads/libpayload')
37
Julius Werner634ccac2014-06-24 13:59:18 -070038_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
Julius Werner634ccac2014-06-24 13:59:18 -070042def ParsePortage(board):
43 """Parse some data from portage files. equery takes ages in comparison."""
44 with open(os.path.join('/build', board, 'packages/Packages'), 'r') as f:
Julius Werner634ccac2014-06-24 13:59:18 -070045 for line in f:
46 if line[:7] == 'CHOST: ':
Julius Werner193c9142018-05-09 16:25:38 -070047 return line[7:].strip()
Julius Werner634ccac2014-06-24 13:59:18 -070048
49
50def ParseArgs(argv):
51 """Parse and validate command line arguments."""
52 parser = commandline.ArgumentParser(default_log_level='warning')
53
54 parser.add_argument('-b', '--board',
55 help='The board overlay name (auto-detect by default)')
Raul E Rangel72f32712018-05-29 11:42:59 -060056 parser.add_argument('-c', '--cgdb', action='store_true',
57 help='Use cgdb curses interface rather than plain gdb')
Julius Werner634ccac2014-06-24 13:59:18 -070058 parser.add_argument('-s', '--symbols',
59 help='Root directory or complete path to symbolized ELF '
60 '(defaults to /build/<BOARD>/firmware)')
61 parser.add_argument('-r', '--reboot', choices=['yes', 'no', 'auto'],
62 help='Reboot the DUT before connect (default: reboot if '
63 'the remote and is unreachable)', default='auto')
64 parser.add_argument('-e', '--execute', action='append', default=[],
65 help='GDB command to run after connect (can be supplied '
66 'multiple times)')
67
68 parser.add_argument('-n', '--servod-name', dest='name')
69 parser.add_argument('--servod-rcfile', default=multiservo.DEFAULT_RC_FILE)
70 parser.add_argument('--servod-server')
71 parser.add_argument('-p', '--servod-port', type=int, dest='port')
72 parser.add_argument('-t', '--tty',
73 help='TTY file to connect to (defaults to cpu_uart_pty)')
74
75 opts = parser.parse_args(argv)
76 multiservo.get_env_options(logging, opts)
77 if opts.name:
78 rc = multiservo.parse_rc(logging, opts.servod_rcfile)
79 if opts.name not in rc:
80 raise parser.error('%s not in %s' % (opts.name, opts.servod_rcfile))
81 if not opts.servod_server:
82 opts.servod_server = rc[opts.name]['sn']
83 if not opts.port:
84 opts.port = rc[opts.name].get('port', client.DEFAULT_PORT)
85 if not opts.board and 'board' in rc[opts.name]:
86 opts.board = rc[opts.name]['board']
Bertrand SIMONNET0ddf7762015-05-20 14:38:00 -070087 logging.warning('Inferring board %s from %s; make sure this is correct!',
88 opts.board, opts.servod_rcfile)
Julius Werner634ccac2014-06-24 13:59:18 -070089
90 if not opts.servod_server:
91 opts.servod_server = client.DEFAULT_HOST
92 if not opts.port:
93 opts.port = client.DEFAULT_PORT
94
95 return opts
96
97
Julius Werner193c9142018-05-09 16:25:38 -070098def FindSymbols(firmware_dir, board):
Julius Werner634ccac2014-06-24 13:59:18 -070099 """Find the symbolized depthcharge ELF (may be supplied by -s flag)."""
Raul E Rangeleb8aadc2018-05-17 09:10:27 -0600100
101 # Allow overriding the file directly just in case our detection screws up.
102 if firmware_dir and firmware_dir.endswith('.elf'):
103 return firmware_dir
104
105 if not firmware_dir:
106 # Unified builds have the format
107 # /build/<board|family>/firmware/<build_target|model>/. The board in
108 # depthcharge corresponds to the build_target in unified builds. For this
109 # reason we need to glob all boards to find the correct build_target.
110 unified_build_dirs = glob.glob('/build/*/firmware/%s' % board)
111 if len(unified_build_dirs) == 1:
112 firmware_dir = unified_build_dirs[0]
113 elif len(unified_build_dirs) > 1:
114 raise ValueError(
115 'Multiple boards were found (%s). Use -s to specify manually' %
116 (', '.join(unified_build_dirs)))
117
Julius Werner634ccac2014-06-24 13:59:18 -0700118 if not firmware_dir:
119 firmware_dir = os.path.join(cros_build_lib.GetSysroot(board), 'firmware')
Julius Werner634ccac2014-06-24 13:59:18 -0700120
Julius Werner193c9142018-05-09 16:25:38 -0700121 # Very old firmware you might still find on GoldenEye had dev.ro.elf.
122 basenames = ['dev.elf', 'dev.ro.elf']
123 for basename in basenames:
124 path = os.path.join(firmware_dir, 'depthcharge', basename)
125 if not os.path.exists(path):
126 path = os.path.join(firmware_dir, basename)
127 if os.path.exists(path):
128 logging.warning('Auto-detected symbol file at %s... make sure that this'
129 ' matches the image on your DUT!', path)
130 return path
Julius Werner634ccac2014-06-24 13:59:18 -0700131
Julius Werner193c9142018-05-09 16:25:38 -0700132 raise ValueError('Could not find depthcharge symbol file (dev.elf)! '
133 '(You can use -s to supply it manually.)')
Julius Werner634ccac2014-06-24 13:59:18 -0700134
135
136# TODO(jwerner): Fine tune |wait| delay or maybe even make it configurable if
137# this causes problems due to load on the host. The callers where this is
138# critical should all have their own timeouts now, though, so it's questionable
139# whether the delay here is even needed at all anymore.
140def ReadAll(fd, wait=0.03):
141 """Read from |fd| until no more data has come for at least |wait| seconds."""
142 data = ''
143 try:
144 while True:
145 time.sleep(wait)
Julius Werner193c9142018-05-09 16:25:38 -0700146 new_data = os.read(fd, 4096)
147 if not new_data:
148 break
149 data += new_data
Julius Werner634ccac2014-06-24 13:59:18 -0700150 except OSError as e:
Julius Werner193c9142018-05-09 16:25:38 -0700151 if e.errno != errno.EAGAIN:
152 raise
153 logging.debug(data)
154 return data
Julius Werner634ccac2014-06-24 13:59:18 -0700155
156
157def GdbChecksum(message):
158 """Calculate a remote-GDB style checksum."""
159 chksum = sum([ord(x) for x in message])
160 return ('%.2x' % chksum)[-2:]
161
162
163def TestConnection(fd):
164 """Return True iff there is a resposive GDB stub on the other end of 'fd'."""
165 cmd = 'vUnknownCommand'
166 for _ in xrange(3):
167 os.write(fd, '$%s#%s\n' % (cmd, GdbChecksum(cmd)))
168 reply = ReadAll(fd)
169 if '+$#00' in reply:
170 os.write(fd, '+')
Mike Frysinger2403af42015-04-30 06:25:03 -0400171 logging.info('TestConnection: Could successfully connect to remote end.')
Julius Werner634ccac2014-06-24 13:59:18 -0700172 return True
Mike Frysinger2403af42015-04-30 06:25:03 -0400173 logging.info('TestConnection: Remote end does not respond.')
Julius Werner634ccac2014-06-24 13:59:18 -0700174 return False
175
176
177def main(argv):
178 opts = ParseArgs(argv)
179 servo = client.ServoClient(host=opts.servod_server, port=opts.port)
180
181 if not opts.tty:
182 try:
183 opts.tty = servo.get('cpu_uart_pty')
184 except (client.ServoClientError, socket.error):
Mike Frysinger2403af42015-04-30 06:25:03 -0400185 logging.error('Cannot auto-detect TTY file without servod. Use the --tty '
186 'option.')
Julius Werner634ccac2014-06-24 13:59:18 -0700187 raise
Aseda Aboagye6e21d3f2016-10-21 11:37:56 -0700188 with terminal_freezer.TerminalFreezer(opts.tty):
Julius Werner634ccac2014-06-24 13:59:18 -0700189 fd = os.open(opts.tty, os.O_RDWR | os.O_NONBLOCK)
190
191 data = ReadAll(fd)
192 if opts.reboot == 'auto':
193 if TestConnection(fd):
194 opts.reboot = 'no'
195 else:
196 opts.reboot = 'yes'
197
198 if opts.reboot == 'yes':
Mike Frysinger2403af42015-04-30 06:25:03 -0400199 logging.info('Rebooting DUT...')
Julius Werner634ccac2014-06-24 13:59:18 -0700200 try:
201 servo.set('warm_reset', 'on')
202 time.sleep(0.1)
203 servo.set('warm_reset', 'off')
204 except (client.ServoClientError, socket.error):
Mike Frysinger2403af42015-04-30 06:25:03 -0400205 logging.error('Cannot reboot without a Servo board. You have to boot '
206 'into developer mode and press CTRL+G manually before '
207 'running fwgdb.')
Julius Werner634ccac2014-06-24 13:59:18 -0700208 raise
209
210 # Throw away old data to avoid confusion from messages before the reboot
211 data = ''
Julius Werner193c9142018-05-09 16:25:38 -0700212 msg = ('Could not reboot into depthcharge!')
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500213 with timeout_util.Timeout(10, msg):
Julius Werner193c9142018-05-09 16:25:38 -0700214 while not re.search(_PTRN_BOARD, data):
Julius Werner634ccac2014-06-24 13:59:18 -0700215 data += ReadAll(fd)
216
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500217 msg = ('Could not enter GDB mode with CTRL+G! '
Julius Werner193c9142018-05-09 16:25:38 -0700218 '(Confirm that you flashed an "image.dev.bin" image to this DUT, '
219 'and that you have GBB_FLAG_FORCE_DEV_SWITCH_ON (0x8) set.)')
220 with timeout_util.Timeout(5, msg):
221 while not re.search(_PTRN_GDB, data):
222 # Send a CTRL+G to tell depthcharge to trap into GDB.
223 logging.debug('[Ctrl+G]')
224 os.write(fd, chr(ord('G') & 0x1f))
Julius Werner634ccac2014-06-24 13:59:18 -0700225 data += ReadAll(fd)
226
227 if not opts.board:
228 matches = re.findall(_PTRN_BOARD, data)
229 if not matches:
230 raise ValueError('Could not auto-detect board! Please use -b option.')
231 opts.board = matches[-1]
Mike Frysinger2403af42015-04-30 06:25:03 -0400232 logging.info('Auto-detected board as %s from DUT console output.',
233 opts.board)
Julius Werner634ccac2014-06-24 13:59:18 -0700234
235 if not TestConnection(fd):
236 raise IOError('Could not connect to remote end! Confirm that your DUT is '
237 'running in GDB mode on %s.' % opts.tty)
238
239 # Eat up leftover data or it will spill back to terminal
240 ReadAll(fd)
241 os.close(fd)
242
243 opts.execute.insert(0, 'target remote %s' % opts.tty)
244 ex_args = sum([['--ex', cmd] for cmd in opts.execute], [])
245
Julius Werner193c9142018-05-09 16:25:38 -0700246 chost = ParsePortage(opts.board)
Mike Frysinger2403af42015-04-30 06:25:03 -0400247 logging.info('Launching GDB...')
Raul E Rangel72f32712018-05-29 11:42:59 -0600248
249 gdb_cmd = chost + '-gdb'
250 gdb_args = [
251 '--symbols', FindSymbols(opts.symbols, opts.board),
252 '--directory', _SRC_DC,
253 '--directory', _SRC_VB,
254 '--directory', _SRC_LP,
255 ] + ex_args
256
257 if opts.cgdb:
258 full_cmd = ['cgdb', '-d', gdb_cmd, '--'] + gdb_args
259 else:
260 full_cmd = [gdb_cmd] + gdb_args
261
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500262 cros_build_lib.RunCommand(
Raul E Rangel72f32712018-05-29 11:42:59 -0600263 full_cmd, ignore_sigint=True, debug_level=logging.WARNING)