blob: c3e516d370cdb204191036d60343bcc2e1f76398 [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 Werner149fe782018-10-10 15:18:14 -070023from elftools.elf.elffile import ELFFile
24
Julius Werner634ccac2014-06-24 13:59:18 -070025# Need to do this before Servo import
26cros_build_lib.AssertInsideChroot()
27
Mike Frysinger92bdef52019-08-21 21:05:13 -040028# pylint: disable=import-error,wrong-import-position
Julius Werner634ccac2014-06-24 13:59:18 -070029from servo import client
Ruben Rodriguez Buchillon15bdd452018-10-09 08:31:41 +080030from servo import servo_parsing
Aseda Aboagye6e21d3f2016-10-21 11:37:56 -070031from servo import terminal_freezer
Mike Frysinger92bdef52019-08-21 21:05:13 -040032# pylint: enable=import-error,wrong-import-position
Julius Werner634ccac2014-06-24 13:59:18 -070033
Ralph Nathan91874ca2015-03-19 13:29:41 -070034
Julius Werner634ccac2014-06-24 13:59:18 -070035_SRC_ROOT = os.path.join(constants.CHROOT_SOURCE_ROOT, 'src')
36_SRC_DC = os.path.join(_SRC_ROOT, 'platform/depthcharge')
37_SRC_VB = os.path.join(_SRC_ROOT, 'platform/vboot_reference')
38_SRC_LP = os.path.join(_SRC_ROOT, 'third_party/coreboot/payloads/libpayload')
39
Julius Werner634ccac2014-06-24 13:59:18 -070040_PTRN_GDB = 'Ready for GDB connection'
Julius Werner20dfc992014-07-21 18:40:11 -070041_PTRN_BOARD = 'Starting(?: read-only| read/write)? depthcharge on ([a-z_]+)...'
Julius Werner634ccac2014-06-24 13:59:18 -070042
43
Julius Werner149fe782018-10-10 15:18:14 -070044def GetGdbForElf(elf):
45 """Return the correct C compiler prefix for the target ELF file."""
46 with open(elf, 'rb') as elf:
47 return {
48 'EM_386': 'x86_64-cros-linux-gnu-gdb',
49 'EM_X86_64': 'x86_64-cros-linux-gnu-gdb',
50 'EM_ARM': 'armv7a-cros-linux-gnueabihf-gdb',
51 'EM_AARCH64': 'aarch64-cros-linux-gnu-gdb',
52 }[ELFFile(elf).header.e_machine]
Julius Werner634ccac2014-06-24 13:59:18 -070053
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)')
Raul E Rangel72f32712018-05-29 11:42:59 -060061 parser.add_argument('-c', '--cgdb', action='store_true',
62 help='Use cgdb curses interface rather than plain gdb')
Julius Werner634ccac2014-06-24 13:59:18 -070063 parser.add_argument('-s', '--symbols',
64 help='Root directory or complete path to symbolized ELF '
65 '(defaults to /build/<BOARD>/firmware)')
66 parser.add_argument('-r', '--reboot', choices=['yes', 'no', 'auto'],
67 help='Reboot the DUT before connect (default: reboot if '
68 'the remote and is unreachable)', default='auto')
69 parser.add_argument('-e', '--execute', action='append', default=[],
70 help='GDB command to run after connect (can be supplied '
71 'multiple times)')
Ruben Rodriguez Buchillond7c65bd2018-12-10 10:10:19 +080072 name_flags = ['-n', '--servod-name']
73 parser.add_argument(*name_flags, dest='name')
Ruben Rodriguez Buchillon15bdd452018-10-09 08:31:41 +080074 parser.add_argument('--servod-rcfile', default=servo_parsing.DEFAULT_RC_FILE)
Julius Werner634ccac2014-06-24 13:59:18 -070075 parser.add_argument('--servod-server')
Ruben Rodriguez Buchillond7c65bd2018-12-10 10:10:19 +080076 port_flags = ['-p', '--servod-port']
77 parser.add_argument(*port_flags, type=int, dest='port')
Julius Werner634ccac2014-06-24 13:59:18 -070078 parser.add_argument('-t', '--tty',
79 help='TTY file to connect to (defaults to cpu_uart_pty)')
80
Ruben Rodriguez Buchillond7c65bd2018-12-10 10:10:19 +080081 # Retrieve port from environment variable if not already in cmdline args.
82 servo_parsing.BaseServodParser.HandlePortEnvVar(cmdline=argv,
83 pri_flags=port_flags)
84 # Retrieve name from environment variable if not already in cmdline args.
85 servo_parsing._ServodRCParser.HandleNameEnvVar(cmdline=argv,
86 pri_flags=name_flags)
Julius Werner634ccac2014-06-24 13:59:18 -070087 opts = parser.parse_args(argv)
Ruben Rodriguez Buchillond7c65bd2018-12-10 10:10:19 +080088
89 # Set |.port| and |.board| in |opts| if there's a rc match for opts.name.
90 servo_parsing._ServodRCParser.PostProcessRCElements(opts, opts.servod_rcfile)
Julius Werner634ccac2014-06-24 13:59:18 -070091
92 if not opts.servod_server:
93 opts.servod_server = client.DEFAULT_HOST
94 if not opts.port:
95 opts.port = client.DEFAULT_PORT
96
97 return opts
98
99
Julius Werner193c9142018-05-09 16:25:38 -0700100def FindSymbols(firmware_dir, board):
Julius Werner634ccac2014-06-24 13:59:18 -0700101 """Find the symbolized depthcharge ELF (may be supplied by -s flag)."""
Raul E Rangeleb8aadc2018-05-17 09:10:27 -0600102
103 # Allow overriding the file directly just in case our detection screws up.
104 if firmware_dir and firmware_dir.endswith('.elf'):
105 return firmware_dir
106
107 if not firmware_dir:
108 # Unified builds have the format
109 # /build/<board|family>/firmware/<build_target|model>/. The board in
110 # depthcharge corresponds to the build_target in unified builds. For this
111 # reason we need to glob all boards to find the correct build_target.
112 unified_build_dirs = glob.glob('/build/*/firmware/%s' % board)
113 if len(unified_build_dirs) == 1:
114 firmware_dir = unified_build_dirs[0]
115 elif len(unified_build_dirs) > 1:
116 raise ValueError(
117 'Multiple boards were found (%s). Use -s to specify manually' %
118 (', '.join(unified_build_dirs)))
119
Julius Werner634ccac2014-06-24 13:59:18 -0700120 if not firmware_dir:
121 firmware_dir = os.path.join(cros_build_lib.GetSysroot(board), 'firmware')
Julius Werner634ccac2014-06-24 13:59:18 -0700122
Julius Werner193c9142018-05-09 16:25:38 -0700123 # Very old firmware you might still find on GoldenEye had dev.ro.elf.
124 basenames = ['dev.elf', 'dev.ro.elf']
125 for basename in basenames:
126 path = os.path.join(firmware_dir, 'depthcharge', basename)
127 if not os.path.exists(path):
128 path = os.path.join(firmware_dir, basename)
129 if os.path.exists(path):
130 logging.warning('Auto-detected symbol file at %s... make sure that this'
131 ' matches the image on your DUT!', path)
132 return path
Julius Werner634ccac2014-06-24 13:59:18 -0700133
Julius Werner193c9142018-05-09 16:25:38 -0700134 raise ValueError('Could not find depthcharge symbol file (dev.elf)! '
135 '(You can use -s to supply it manually.)')
Julius Werner634ccac2014-06-24 13:59:18 -0700136
137
138# TODO(jwerner): Fine tune |wait| delay or maybe even make it configurable if
139# this causes problems due to load on the host. The callers where this is
140# critical should all have their own timeouts now, though, so it's questionable
141# whether the delay here is even needed at all anymore.
142def ReadAll(fd, wait=0.03):
143 """Read from |fd| until no more data has come for at least |wait| seconds."""
144 data = ''
145 try:
146 while True:
147 time.sleep(wait)
Julius Werner193c9142018-05-09 16:25:38 -0700148 new_data = os.read(fd, 4096)
149 if not new_data:
150 break
151 data += new_data
Julius Werner634ccac2014-06-24 13:59:18 -0700152 except OSError as e:
Julius Werner193c9142018-05-09 16:25:38 -0700153 if e.errno != errno.EAGAIN:
154 raise
155 logging.debug(data)
156 return data
Julius Werner634ccac2014-06-24 13:59:18 -0700157
158
159def GdbChecksum(message):
160 """Calculate a remote-GDB style checksum."""
161 chksum = sum([ord(x) for x in message])
162 return ('%.2x' % chksum)[-2:]
163
164
165def TestConnection(fd):
166 """Return True iff there is a resposive GDB stub on the other end of 'fd'."""
167 cmd = 'vUnknownCommand'
Mike Frysinger79cca962019-06-13 15:26:53 -0400168 for _ in range(3):
Julius Werner634ccac2014-06-24 13:59:18 -0700169 os.write(fd, '$%s#%s\n' % (cmd, GdbChecksum(cmd)))
170 reply = ReadAll(fd)
171 if '+$#00' in reply:
172 os.write(fd, '+')
Mike Frysinger2403af42015-04-30 06:25:03 -0400173 logging.info('TestConnection: Could successfully connect to remote end.')
Julius Werner634ccac2014-06-24 13:59:18 -0700174 return True
Mike Frysinger2403af42015-04-30 06:25:03 -0400175 logging.info('TestConnection: Remote end does not respond.')
Julius Werner634ccac2014-06-24 13:59:18 -0700176 return False
177
178
179def main(argv):
180 opts = ParseArgs(argv)
181 servo = client.ServoClient(host=opts.servod_server, port=opts.port)
182
183 if not opts.tty:
184 try:
185 opts.tty = servo.get('cpu_uart_pty')
186 except (client.ServoClientError, socket.error):
Mike Frysinger2403af42015-04-30 06:25:03 -0400187 logging.error('Cannot auto-detect TTY file without servod. Use the --tty '
188 'option.')
Julius Werner634ccac2014-06-24 13:59:18 -0700189 raise
Aseda Aboagye6e21d3f2016-10-21 11:37:56 -0700190 with terminal_freezer.TerminalFreezer(opts.tty):
Julius Werner634ccac2014-06-24 13:59:18 -0700191 fd = os.open(opts.tty, os.O_RDWR | os.O_NONBLOCK)
192
193 data = ReadAll(fd)
194 if opts.reboot == 'auto':
195 if TestConnection(fd):
196 opts.reboot = 'no'
197 else:
198 opts.reboot = 'yes'
199
200 if opts.reboot == 'yes':
Mike Frysinger2403af42015-04-30 06:25:03 -0400201 logging.info('Rebooting DUT...')
Julius Werner634ccac2014-06-24 13:59:18 -0700202 try:
203 servo.set('warm_reset', 'on')
204 time.sleep(0.1)
205 servo.set('warm_reset', 'off')
206 except (client.ServoClientError, socket.error):
Mike Frysinger2403af42015-04-30 06:25:03 -0400207 logging.error('Cannot reboot without a Servo board. You have to boot '
208 'into developer mode and press CTRL+G manually before '
209 'running fwgdb.')
Julius Werner634ccac2014-06-24 13:59:18 -0700210 raise
211
212 # Throw away old data to avoid confusion from messages before the reboot
213 data = ''
Julius Werner193c9142018-05-09 16:25:38 -0700214 msg = ('Could not reboot into depthcharge!')
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500215 with timeout_util.Timeout(10, msg):
Julius Werner193c9142018-05-09 16:25:38 -0700216 while not re.search(_PTRN_BOARD, data):
Julius Werner634ccac2014-06-24 13:59:18 -0700217 data += ReadAll(fd)
218
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500219 msg = ('Could not enter GDB mode with CTRL+G! '
Julius Werner193c9142018-05-09 16:25:38 -0700220 '(Confirm that you flashed an "image.dev.bin" image to this DUT, '
221 'and that you have GBB_FLAG_FORCE_DEV_SWITCH_ON (0x8) set.)')
222 with timeout_util.Timeout(5, msg):
223 while not re.search(_PTRN_GDB, data):
224 # Send a CTRL+G to tell depthcharge to trap into GDB.
225 logging.debug('[Ctrl+G]')
226 os.write(fd, chr(ord('G') & 0x1f))
Julius Werner634ccac2014-06-24 13:59:18 -0700227 data += ReadAll(fd)
228
229 if not opts.board:
230 matches = re.findall(_PTRN_BOARD, data)
231 if not matches:
232 raise ValueError('Could not auto-detect board! Please use -b option.')
233 opts.board = matches[-1]
Mike Frysinger2403af42015-04-30 06:25:03 -0400234 logging.info('Auto-detected board as %s from DUT console output.',
235 opts.board)
Julius Werner634ccac2014-06-24 13:59:18 -0700236
237 if not TestConnection(fd):
238 raise IOError('Could not connect to remote end! Confirm that your DUT is '
239 'running in GDB mode on %s.' % opts.tty)
240
241 # Eat up leftover data or it will spill back to terminal
242 ReadAll(fd)
243 os.close(fd)
244
245 opts.execute.insert(0, 'target remote %s' % opts.tty)
246 ex_args = sum([['--ex', cmd] for cmd in opts.execute], [])
247
Julius Werner149fe782018-10-10 15:18:14 -0700248 elf = FindSymbols(opts.symbols, opts.board)
249 gdb_cmd = GetGdbForElf(elf)
Raul E Rangel72f32712018-05-29 11:42:59 -0600250
Raul E Rangel72f32712018-05-29 11:42:59 -0600251 gdb_args = [
Julius Werner149fe782018-10-10 15:18:14 -0700252 '--symbols', elf,
Raul E Rangel72f32712018-05-29 11:42:59 -0600253 '--directory', _SRC_DC,
254 '--directory', _SRC_VB,
255 '--directory', _SRC_LP,
256 ] + ex_args
257
258 if opts.cgdb:
259 full_cmd = ['cgdb', '-d', gdb_cmd, '--'] + gdb_args
260 else:
261 full_cmd = [gdb_cmd] + gdb_args
262
Julius Werner149fe782018-10-10 15:18:14 -0700263 logging.info('Launching GDB...')
Mike Frysinger45602c72019-09-22 02:15:11 -0400264 cros_build_lib.run(
Raul E Rangel72f32712018-05-29 11:42:59 -0600265 full_cmd, ignore_sigint=True, debug_level=logging.WARNING)