blob: 7186e006f9f49d8e7b147ac3ccb84cdfc0f3a553 [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 Frysinger1d4752b2014-11-08 04:00:18 -05007# pylint: disable=bad-continuation
8
Mike Frysinger383367e2014-09-16 15:06:17 -04009from __future__ import print_function
10
Julius Werner634ccac2014-06-24 13:59:18 -070011import errno
12import logging
13import os
14import re
15import signal
16import socket
17import time
18
19from chromite.cbuildbot import constants
20from chromite.lib import commandline
21from chromite.lib import cros_build_lib
22from chromite.lib import timeout_util
23
24# pylint: disable=W0622
25from chromite.lib.cros_build_lib import Error, Warning, Info, Debug
26
27# Need to do this before Servo import
28cros_build_lib.AssertInsideChroot()
29
30from servo import client
31from servo import multiservo
32
33_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
38_PTRN_DEVMODE = 'Entering VbBootDeveloper()'
39_PTRN_GDB = 'Ready for GDB connection'
Julius Werner20dfc992014-07-21 18:40:11 -070040_PTRN_BOARD = 'Starting(?: read-only| read/write)? depthcharge on ([a-z_]+)...'
Julius Werner634ccac2014-06-24 13:59:18 -070041
42
43class TerminalFreezer(object):
44 """SIGSTOP all processes (and their parents) that have the TTY open."""
45
46 def __init__(self, tty):
47 self._tty = tty
48 self._processes = None
49
50 def __enter__(self):
51 lsof = cros_build_lib.RunCommand(['lsof', '-FR', self._tty],
52 capture_output=True, log_output=True, error_code_ok=True)
53 self._processes = re.findall(r'^(?:R|p)(\d+)$', lsof.output, re.MULTILINE)
54
55 # SIGSTOP parents before children
56 try:
57 for p in reversed(self._processes):
58 Info('Sending SIGSTOP to process %s!', p)
59 time.sleep(0.02)
60 os.kill(int(p), signal.SIGSTOP)
61 except OSError:
62 self.__exit__(None, None, None)
63 raise
64
65 def __exit__(self, _t, _v, _b):
66 # ...and wake 'em up again in reverse order
67 for p in self._processes:
68 Info('Sending SIGCONT to process %s!', p)
69 try:
70 os.kill(int(p), signal.SIGCONT)
71 except OSError as e:
72 Error("Error when trying to unfreeze process %s: %s" % (p, str(e)))
73
74
75def ParsePortage(board):
76 """Parse some data from portage files. equery takes ages in comparison."""
77 with open(os.path.join('/build', board, 'packages/Packages'), 'r') as f:
78 chost = None
79 use = None
80 for line in f:
81 if line[:7] == 'CHOST: ':
82 chost = line[7:].strip()
83 if line[:5] == 'USE: ':
84 use = line[5:].strip()
85 if chost and use:
86 return (chost, use)
87
88
89def ParseArgs(argv):
90 """Parse and validate command line arguments."""
91 parser = commandline.ArgumentParser(default_log_level='warning')
92
93 parser.add_argument('-b', '--board',
94 help='The board overlay name (auto-detect by default)')
95 parser.add_argument('-s', '--symbols',
96 help='Root directory or complete path to symbolized ELF '
97 '(defaults to /build/<BOARD>/firmware)')
98 parser.add_argument('-r', '--reboot', choices=['yes', 'no', 'auto'],
99 help='Reboot the DUT before connect (default: reboot if '
100 'the remote and is unreachable)', default='auto')
101 parser.add_argument('-e', '--execute', action='append', default=[],
102 help='GDB command to run after connect (can be supplied '
103 'multiple times)')
104
105 parser.add_argument('-n', '--servod-name', dest='name')
106 parser.add_argument('--servod-rcfile', default=multiservo.DEFAULT_RC_FILE)
107 parser.add_argument('--servod-server')
108 parser.add_argument('-p', '--servod-port', type=int, dest='port')
109 parser.add_argument('-t', '--tty',
110 help='TTY file to connect to (defaults to cpu_uart_pty)')
111
112 opts = parser.parse_args(argv)
113 multiservo.get_env_options(logging, opts)
114 if opts.name:
115 rc = multiservo.parse_rc(logging, opts.servod_rcfile)
116 if opts.name not in rc:
117 raise parser.error('%s not in %s' % (opts.name, opts.servod_rcfile))
118 if not opts.servod_server:
119 opts.servod_server = rc[opts.name]['sn']
120 if not opts.port:
121 opts.port = rc[opts.name].get('port', client.DEFAULT_PORT)
122 if not opts.board and 'board' in rc[opts.name]:
123 opts.board = rc[opts.name]['board']
124 Warning('Inferring board %s from %s... make sure this is correct!',
125 opts.board, opts.servod_rcfile)
126
127 if not opts.servod_server:
128 opts.servod_server = client.DEFAULT_HOST
129 if not opts.port:
130 opts.port = client.DEFAULT_PORT
131
132 return opts
133
134
135def FindSymbols(firmware_dir, board, use):
136 """Find the symbolized depthcharge ELF (may be supplied by -s flag)."""
137 if not firmware_dir:
138 firmware_dir = os.path.join(cros_build_lib.GetSysroot(board), 'firmware')
139 # Allow overriding the file directly just in case our detection screws up
140 if firmware_dir.endswith('.elf'):
141 return firmware_dir
142
143 if 'unified_depthcharge' in use:
Julius Werner20dfc992014-07-21 18:40:11 -0700144 basename = 'dev.elf'
Julius Werner634ccac2014-06-24 13:59:18 -0700145 else:
Julius Werner20dfc992014-07-21 18:40:11 -0700146 basename = 'dev.ro.elf'
Julius Werner634ccac2014-06-24 13:59:18 -0700147
Julius Werner20dfc992014-07-21 18:40:11 -0700148 path = os.path.join(firmware_dir, 'depthcharge', basename)
Julius Werner634ccac2014-06-24 13:59:18 -0700149 if not os.path.exists(path):
150 path = os.path.join(firmware_dir, basename)
151
152 if os.path.exists(path):
153 Warning('Auto-detected symbol file at %s... make sure that this matches '
154 'the image on your DUT!', path)
155 return path
156
157 raise ValueError('Could not find %s symbol file!' % basename)
158
159
160# TODO(jwerner): Fine tune |wait| delay or maybe even make it configurable if
161# this causes problems due to load on the host. The callers where this is
162# critical should all have their own timeouts now, though, so it's questionable
163# whether the delay here is even needed at all anymore.
164def ReadAll(fd, wait=0.03):
165 """Read from |fd| until no more data has come for at least |wait| seconds."""
166 data = ''
167 try:
168 while True:
169 time.sleep(wait)
170 data += os.read(fd, 4096)
171 except OSError as e:
172 if e.errno == errno.EAGAIN:
173 Debug(data)
174 return data
175 raise
176
177
178def GdbChecksum(message):
179 """Calculate a remote-GDB style checksum."""
180 chksum = sum([ord(x) for x in message])
181 return ('%.2x' % chksum)[-2:]
182
183
184def TestConnection(fd):
185 """Return True iff there is a resposive GDB stub on the other end of 'fd'."""
186 cmd = 'vUnknownCommand'
187 for _ in xrange(3):
188 os.write(fd, '$%s#%s\n' % (cmd, GdbChecksum(cmd)))
189 reply = ReadAll(fd)
190 if '+$#00' in reply:
191 os.write(fd, '+')
192 Info('TestConnection: Could successfully connect to remote end.')
193 return True
194 Info('TestConnection: Remote end does not respond.')
195 return False
196
197
198def main(argv):
199 opts = ParseArgs(argv)
200 servo = client.ServoClient(host=opts.servod_server, port=opts.port)
201
202 if not opts.tty:
203 try:
204 opts.tty = servo.get('cpu_uart_pty')
205 except (client.ServoClientError, socket.error):
206 Error('Cannot auto-detect TTY file without servod. Use the --tty option.')
207 raise
208 with TerminalFreezer(opts.tty):
209 fd = os.open(opts.tty, os.O_RDWR | os.O_NONBLOCK)
210
211 data = ReadAll(fd)
212 if opts.reboot == 'auto':
213 if TestConnection(fd):
214 opts.reboot = 'no'
215 else:
216 opts.reboot = 'yes'
217
218 if opts.reboot == 'yes':
219 Info('Rebooting DUT...')
220 try:
221 servo.set('warm_reset', 'on')
222 time.sleep(0.1)
223 servo.set('warm_reset', 'off')
224 except (client.ServoClientError, socket.error):
225 Error('Cannot reboot without a Servo board. You have to boot into '
226 'developer mode and press CTRL+G manually before running fwgdb.')
227 raise
228
229 # Throw away old data to avoid confusion from messages before the reboot
230 data = ''
231 with timeout_util.Timeout(10, 'Could not reboot into developer mode! '
232 '(Confirm that you have GBB_FLAG_FORCE_DEV_SWITCH_ON (0x8) set.)'):
233 while _PTRN_DEVMODE not in data:
234 data += ReadAll(fd)
235
236 # Send a CTRL+G
237 Info('Developer mode detected, pressing CTRL+G...')
238 os.write(fd, chr(ord('G') & 0x1f))
239
240 with timeout_util.Timeout(1, 'Could not enter GDB mode with CTRL+G! '
241 '(Confirm that you flashed an "image.dev.bin" image to this DUT.)'):
242 while _PTRN_GDB not in data:
243 data += ReadAll(fd)
244
245 if not opts.board:
246 matches = re.findall(_PTRN_BOARD, data)
247 if not matches:
248 raise ValueError('Could not auto-detect board! Please use -b option.')
249 opts.board = matches[-1]
250 Info('Auto-detected board as %s from DUT console output.', opts.board)
251
252 if not TestConnection(fd):
253 raise IOError('Could not connect to remote end! Confirm that your DUT is '
254 'running in GDB mode on %s.' % opts.tty)
255
256 # Eat up leftover data or it will spill back to terminal
257 ReadAll(fd)
258 os.close(fd)
259
260 opts.execute.insert(0, 'target remote %s' % opts.tty)
261 ex_args = sum([['--ex', cmd] for cmd in opts.execute], [])
262
263 chost, use = ParsePortage(opts.board)
264 Info('Launching GDB...')
265 cros_build_lib.RunCommand([chost + '-gdb',
266 '--symbols', FindSymbols(opts.symbols, opts.board, use),
267 '--directory', _SRC_DC,
268 '--directory', _SRC_VB,
269 '--directory', _SRC_LP] + ex_args,
270 ignore_sigint=True, debug_level=logging.WARNING)