blob: 351233e703df0799054c76028cd7fb284ce3128e [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
7import errno
Raul E Rangeleb8aadc2018-05-17 09:10:27 -06008import glob
Chris McDonald59650c32021-07-20 15:29:28 -06009import logging
Julius Werner634ccac2014-06-24 13:59:18 -070010import os
11import re
Julius Werner634ccac2014-06-24 13:59:18 -070012import socket
13import time
14
Chris McDonald59650c32021-07-20 15:29:28 -060015from chromite.third_party.pyelftools.elftools.elf.elffile import ELFFile
16
Mike Frysinger06a51c82021-04-06 11:39:17 -040017from chromite.lib import build_target_lib
Aviv Keshetb7519e12016-10-04 00:50:00 -070018from chromite.lib import constants
Julius Werner634ccac2014-06-24 13:59:18 -070019from chromite.lib import cros_build_lib
20from chromite.lib import timeout_util
Chris McDonald59650c32021-07-20 15:29:28 -060021
Julius Werner634ccac2014-06-24 13:59:18 -070022
Julius Werner634ccac2014-06-24 13:59:18 -070023# Need to do this before Servo import
24cros_build_lib.AssertInsideChroot()
25
Mike Frysinger92bdef52019-08-21 21:05:13 -040026# pylint: disable=import-error,wrong-import-position
Julius Werner634ccac2014-06-24 13:59:18 -070027from servo import client
Ruben Rodriguez Buchillon15bdd452018-10-09 08:31:41 +080028from servo import servo_parsing
Aseda Aboagye6e21d3f2016-10-21 11:37:56 -070029from servo import terminal_freezer
Chris McDonald59650c32021-07-20 15:29:28 -060030
31
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
Douglas Anderson62dd87e2021-08-13 11:08:54 -070040_PTRN_GDB = b'Ready for GDB connection'
Douglas Anderson5a1056b2021-08-26 14:10:08 -070041_PTRN_BOARD = (
42 b'Starting(?: read-only| read/write)? depthcharge on ([A-Za-z_]+)...')
Julius Werner634ccac2014-06-24 13:59:18 -070043
44
Julius Werner149fe782018-10-10 15:18:14 -070045def GetGdbForElf(elf):
46 """Return the correct C compiler prefix for the target ELF file."""
Mike Frysinger17844a02019-08-24 18:21:02 -040047 with open(elf, 'rb') as fp:
Julius Werner149fe782018-10-10 15:18:14 -070048 return {
49 'EM_386': 'x86_64-cros-linux-gnu-gdb',
50 'EM_X86_64': 'x86_64-cros-linux-gnu-gdb',
51 'EM_ARM': 'armv7a-cros-linux-gnueabihf-gdb',
52 'EM_AARCH64': 'aarch64-cros-linux-gnu-gdb',
Mike Frysinger17844a02019-08-24 18:21:02 -040053 }[ELFFile(fp).header.e_machine]
Julius Werner634ccac2014-06-24 13:59:18 -070054
55
56def ParseArgs(argv):
57 """Parse and validate command line arguments."""
Raul E Rangelb54ec042020-02-06 09:25:12 -070058 description = 'Debug depthcharge using GDB'
59 parser = servo_parsing.ServodClientParser(description=description)
Julius Werner634ccac2014-06-24 13:59:18 -070060 parser.add_argument('-b', '--board',
61 help='The board overlay name (auto-detect by default)')
Raul E Rangel72f32712018-05-29 11:42:59 -060062 parser.add_argument('-c', '--cgdb', action='store_true',
63 help='Use cgdb curses interface rather than plain gdb')
Raul E Rangelb54ec042020-02-06 09:25:12 -070064 parser.add_argument('-y', '--symbols',
Julius Werner634ccac2014-06-24 13:59:18 -070065 help='Root directory or complete path to symbolized ELF '
66 '(defaults to /build/<BOARD>/firmware)')
67 parser.add_argument('-r', '--reboot', choices=['yes', 'no', 'auto'],
68 help='Reboot the DUT before connect (default: reboot if '
69 'the remote and is unreachable)', default='auto')
70 parser.add_argument('-e', '--execute', action='append', default=[],
71 help='GDB command to run after connect (can be supplied '
72 'multiple times)')
Julius Werner634ccac2014-06-24 13:59:18 -070073 parser.add_argument('-t', '--tty',
74 help='TTY file to connect to (defaults to cpu_uart_pty)')
Julius Werner634ccac2014-06-24 13:59:18 -070075 opts = parser.parse_args(argv)
Ruben Rodriguez Buchillond7c65bd2018-12-10 10:10:19 +080076
Julius Werner634ccac2014-06-24 13:59:18 -070077 return opts
78
79
Julius Werner193c9142018-05-09 16:25:38 -070080def FindSymbols(firmware_dir, board):
Douglas Anderson69e5e702021-08-13 11:10:19 -070081 """Find the symbolized depthcharge ELF (may be supplied by -y flag)."""
Raul E Rangeleb8aadc2018-05-17 09:10:27 -060082
83 # Allow overriding the file directly just in case our detection screws up.
84 if firmware_dir and firmware_dir.endswith('.elf'):
85 return firmware_dir
86
87 if not firmware_dir:
88 # Unified builds have the format
89 # /build/<board|family>/firmware/<build_target|model>/. The board in
90 # depthcharge corresponds to the build_target in unified builds. For this
91 # reason we need to glob all boards to find the correct build_target.
92 unified_build_dirs = glob.glob('/build/*/firmware/%s' % board)
93 if len(unified_build_dirs) == 1:
94 firmware_dir = unified_build_dirs[0]
95 elif len(unified_build_dirs) > 1:
96 raise ValueError(
Douglas Anderson69e5e702021-08-13 11:10:19 -070097 'Multiple boards were found (%s). Use -y to specify manually' %
Raul E Rangeleb8aadc2018-05-17 09:10:27 -060098 (', '.join(unified_build_dirs)))
99
Julius Werner634ccac2014-06-24 13:59:18 -0700100 if not firmware_dir:
Mike Frysinger06a51c82021-04-06 11:39:17 -0400101 firmware_dir = os.path.join(
102 build_target_lib.get_default_sysroot_path(board), 'firmware')
Julius Werner634ccac2014-06-24 13:59:18 -0700103
Julius Werner193c9142018-05-09 16:25:38 -0700104 # Very old firmware you might still find on GoldenEye had dev.ro.elf.
105 basenames = ['dev.elf', 'dev.ro.elf']
106 for basename in basenames:
107 path = os.path.join(firmware_dir, 'depthcharge', basename)
108 if not os.path.exists(path):
109 path = os.path.join(firmware_dir, basename)
110 if os.path.exists(path):
111 logging.warning('Auto-detected symbol file at %s... make sure that this'
112 ' matches the image on your DUT!', path)
113 return path
Julius Werner634ccac2014-06-24 13:59:18 -0700114
Julius Werner193c9142018-05-09 16:25:38 -0700115 raise ValueError('Could not find depthcharge symbol file (dev.elf)! '
116 '(You can use -s to supply it manually.)')
Julius Werner634ccac2014-06-24 13:59:18 -0700117
118
119# TODO(jwerner): Fine tune |wait| delay or maybe even make it configurable if
120# this causes problems due to load on the host. The callers where this is
121# critical should all have their own timeouts now, though, so it's questionable
122# whether the delay here is even needed at all anymore.
123def ReadAll(fd, wait=0.03):
124 """Read from |fd| until no more data has come for at least |wait| seconds."""
Douglas Anderson62dd87e2021-08-13 11:08:54 -0700125 data = []
Julius Werner634ccac2014-06-24 13:59:18 -0700126 try:
127 while True:
128 time.sleep(wait)
Julius Werner193c9142018-05-09 16:25:38 -0700129 new_data = os.read(fd, 4096)
130 if not new_data:
131 break
Douglas Anderson62dd87e2021-08-13 11:08:54 -0700132 data.append(new_data)
Julius Werner634ccac2014-06-24 13:59:18 -0700133 except OSError as e:
Julius Werner193c9142018-05-09 16:25:38 -0700134 if e.errno != errno.EAGAIN:
135 raise
Douglas Anderson62dd87e2021-08-13 11:08:54 -0700136 data = b''.join(data)
Julius Wernerbb98c222022-06-23 16:08:35 -0700137 if data:
138 logging.debug(data.decode('ascii', errors='replace'))
Julius Werner193c9142018-05-09 16:25:38 -0700139 return data
Julius Werner634ccac2014-06-24 13:59:18 -0700140
141
142def GdbChecksum(message):
143 """Calculate a remote-GDB style checksum."""
Douglas Anderson62dd87e2021-08-13 11:08:54 -0700144 chksum = sum(message)
145 return (b'%.2x' % chksum)[-2:]
Julius Werner634ccac2014-06-24 13:59:18 -0700146
147
148def TestConnection(fd):
149 """Return True iff there is a resposive GDB stub on the other end of 'fd'."""
Douglas Anderson62dd87e2021-08-13 11:08:54 -0700150 cmd = b'vUnknownCommand'
Mike Frysinger79cca962019-06-13 15:26:53 -0400151 for _ in range(3):
Douglas Anderson62dd87e2021-08-13 11:08:54 -0700152 os.write(fd, b'$%s#%s\n' % (cmd, GdbChecksum(cmd)))
Julius Werner634ccac2014-06-24 13:59:18 -0700153 reply = ReadAll(fd)
Douglas Anderson62dd87e2021-08-13 11:08:54 -0700154 if b'+$#00' in reply:
155 os.write(fd, b'+')
Mike Frysinger2403af42015-04-30 06:25:03 -0400156 logging.info('TestConnection: Could successfully connect to remote end.')
Julius Werner634ccac2014-06-24 13:59:18 -0700157 return True
Mike Frysinger2403af42015-04-30 06:25:03 -0400158 logging.info('TestConnection: Remote end does not respond.')
Julius Werner634ccac2014-06-24 13:59:18 -0700159 return False
160
161
162def main(argv):
163 opts = ParseArgs(argv)
Raul E Rangelb54ec042020-02-06 09:25:12 -0700164 servo = client.ServoClient(host=opts.host, port=opts.port)
Julius Werner634ccac2014-06-24 13:59:18 -0700165
166 if not opts.tty:
167 try:
168 opts.tty = servo.get('cpu_uart_pty')
169 except (client.ServoClientError, socket.error):
Mike Frysinger2403af42015-04-30 06:25:03 -0400170 logging.error('Cannot auto-detect TTY file without servod. Use the --tty '
171 'option.')
Julius Werner634ccac2014-06-24 13:59:18 -0700172 raise
Aseda Aboagye6e21d3f2016-10-21 11:37:56 -0700173 with terminal_freezer.TerminalFreezer(opts.tty):
Julius Werner634ccac2014-06-24 13:59:18 -0700174 fd = os.open(opts.tty, os.O_RDWR | os.O_NONBLOCK)
175
176 data = ReadAll(fd)
177 if opts.reboot == 'auto':
178 if TestConnection(fd):
179 opts.reboot = 'no'
180 else:
181 opts.reboot = 'yes'
182
183 if opts.reboot == 'yes':
Mike Frysinger2403af42015-04-30 06:25:03 -0400184 logging.info('Rebooting DUT...')
Julius Werner634ccac2014-06-24 13:59:18 -0700185 try:
186 servo.set('warm_reset', 'on')
187 time.sleep(0.1)
188 servo.set('warm_reset', 'off')
189 except (client.ServoClientError, socket.error):
Mike Frysinger2403af42015-04-30 06:25:03 -0400190 logging.error('Cannot reboot without a Servo board. You have to boot '
191 'into developer mode and press CTRL+G manually before '
192 'running fwgdb.')
Julius Werner634ccac2014-06-24 13:59:18 -0700193 raise
194
195 # Throw away old data to avoid confusion from messages before the reboot
Douglas Anderson62dd87e2021-08-13 11:08:54 -0700196 data = b''
Julius Werner193c9142018-05-09 16:25:38 -0700197 msg = ('Could not reboot into depthcharge!')
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500198 with timeout_util.Timeout(10, msg):
Julius Werner193c9142018-05-09 16:25:38 -0700199 while not re.search(_PTRN_BOARD, data):
Julius Werner634ccac2014-06-24 13:59:18 -0700200 data += ReadAll(fd)
201
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500202 msg = ('Could not enter GDB mode with CTRL+G! '
Julius Werner193c9142018-05-09 16:25:38 -0700203 '(Confirm that you flashed an "image.dev.bin" image to this DUT, '
204 'and that you have GBB_FLAG_FORCE_DEV_SWITCH_ON (0x8) set.)')
205 with timeout_util.Timeout(5, msg):
206 while not re.search(_PTRN_GDB, data):
Julius Wernerbb98c222022-06-23 16:08:35 -0700207 # Some delay to avoid spamming the console too hard while not being
208 # long enough to cause a user-visible slowdown.
209 time.sleep(0.5)
Julius Werner193c9142018-05-09 16:25:38 -0700210 # Send a CTRL+G to tell depthcharge to trap into GDB.
211 logging.debug('[Ctrl+G]')
Douglas Anderson62dd87e2021-08-13 11:08:54 -0700212 os.write(fd, b'\x07')
Julius Werner634ccac2014-06-24 13:59:18 -0700213 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
Julius Werner149fe782018-10-10 15:18:14 -0700234 elf = FindSymbols(opts.symbols, opts.board)
235 gdb_cmd = GetGdbForElf(elf)
Raul E Rangel72f32712018-05-29 11:42:59 -0600236
Raul E Rangel72f32712018-05-29 11:42:59 -0600237 gdb_args = [
Julius Werner149fe782018-10-10 15:18:14 -0700238 '--symbols', elf,
Raul E Rangel72f32712018-05-29 11:42:59 -0600239 '--directory', _SRC_DC,
240 '--directory', _SRC_VB,
241 '--directory', _SRC_LP,
242 ] + ex_args
243
244 if opts.cgdb:
245 full_cmd = ['cgdb', '-d', gdb_cmd, '--'] + gdb_args
246 else:
247 full_cmd = [gdb_cmd] + gdb_args
248
Julius Werner149fe782018-10-10 15:18:14 -0700249 logging.info('Launching GDB...')
Mike Frysinger45602c72019-09-22 02:15:11 -0400250 cros_build_lib.run(
Raul E Rangel72f32712018-05-29 11:42:59 -0600251 full_cmd, ignore_sigint=True, debug_level=logging.WARNING)