blob: b3f34f0ef9cb180189a54727a55acc3efc66c823 [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2014 The ChromiumOS Authors
Julius Werner634ccac2014-06-24 13:59:18 -07002# 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
Alex Klein1699fab2022-09-08 08:46:06 -060035_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")
Julius Werner634ccac2014-06-24 13:59:18 -070039
Alex Klein1699fab2022-09-08 08:46:06 -060040_PTRN_GDB = b"Ready for GDB connection"
Douglas Anderson5a1056b2021-08-26 14:10:08 -070041_PTRN_BOARD = (
Alex Klein1699fab2022-09-08 08:46:06 -060042 b"Starting(?: read-only| read/write)? depthcharge on ([A-Za-z_]+)..."
43)
Julius Werner634ccac2014-06-24 13:59:18 -070044
45
Julius Werner149fe782018-10-10 15:18:14 -070046def GetGdbForElf(elf):
Alex Klein1699fab2022-09-08 08:46:06 -060047 """Return the correct C compiler prefix for the target ELF file."""
48 with open(elf, "rb") as fp:
49 return {
50 "EM_386": "x86_64-cros-linux-gnu-gdb",
51 "EM_X86_64": "x86_64-cros-linux-gnu-gdb",
52 "EM_ARM": "armv7a-cros-linux-gnueabihf-gdb",
53 "EM_AARCH64": "aarch64-cros-linux-gnu-gdb",
54 }[ELFFile(fp).header.e_machine]
Julius Werner634ccac2014-06-24 13:59:18 -070055
56
57def ParseArgs(argv):
Alex Klein1699fab2022-09-08 08:46:06 -060058 """Parse and validate command line arguments."""
59 description = "Debug depthcharge using GDB"
60 parser = servo_parsing.ServodClientParser(description=description)
61 parser.add_argument(
62 "-b", "--board", help="The board overlay name (auto-detect by default)"
63 )
64 parser.add_argument(
65 "-c",
66 "--cgdb",
67 action="store_true",
68 help="Use cgdb curses interface rather than plain gdb",
69 )
70 parser.add_argument(
71 "-y",
72 "--symbols",
73 help="Root directory or complete path to symbolized ELF "
74 "(defaults to /build/<BOARD>/firmware)",
75 )
76 parser.add_argument(
77 "-r",
78 "--reboot",
79 choices=["yes", "no", "auto"],
80 help="Reboot the DUT before connect (default: reboot if "
81 "the remote and is unreachable)",
82 default="auto",
83 )
84 parser.add_argument(
85 "-e",
86 "--execute",
87 action="append",
88 default=[],
89 help="GDB command to run after connect (can be supplied "
90 "multiple times)",
91 )
92 parser.add_argument(
93 "-t", "--tty", help="TTY file to connect to (defaults to cpu_uart_pty)"
94 )
95 opts = parser.parse_args(argv)
Ruben Rodriguez Buchillond7c65bd2018-12-10 10:10:19 +080096
Alex Klein1699fab2022-09-08 08:46:06 -060097 return opts
Julius Werner634ccac2014-06-24 13:59:18 -070098
99
Julius Werner193c9142018-05-09 16:25:38 -0700100def FindSymbols(firmware_dir, board):
Alex Klein1699fab2022-09-08 08:46:06 -0600101 """Find the symbolized depthcharge ELF (may be supplied by -y flag)."""
Raul E Rangeleb8aadc2018-05-17 09:10:27 -0600102
Alex Klein1699fab2022-09-08 08:46:06 -0600103 # 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
Raul E Rangeleb8aadc2018-05-17 09:10:27 -0600106
Alex Klein1699fab2022-09-08 08:46:06 -0600107 if not firmware_dir:
108 # Unified builds have the format
109 # /build/<board|family>/firmware/<build_target|model>/. The board in
Alex Klein68b270c2023-04-14 14:42:50 -0600110 # depthcharge corresponds to the build_target in unified builds. For
111 # this reason we need to glob all boards to find the correct
112 # build_target.
Alex Klein1699fab2022-09-08 08:46:06 -0600113 unified_build_dirs = glob.glob("/build/*/firmware/%s" % board)
114 if len(unified_build_dirs) == 1:
115 firmware_dir = unified_build_dirs[0]
116 elif len(unified_build_dirs) > 1:
117 raise ValueError(
118 "Multiple boards were found (%s). Use -y to specify manually"
119 % (", ".join(unified_build_dirs))
120 )
Raul E Rangeleb8aadc2018-05-17 09:10:27 -0600121
Alex Klein1699fab2022-09-08 08:46:06 -0600122 if not firmware_dir:
123 firmware_dir = os.path.join(
124 build_target_lib.get_default_sysroot_path(board), "firmware"
125 )
Julius Werner634ccac2014-06-24 13:59:18 -0700126
Alex Klein1699fab2022-09-08 08:46:06 -0600127 # Very old firmware you might still find on GoldenEye had dev.ro.elf.
128 basenames = ["dev.elf", "dev.ro.elf"]
129 for basename in basenames:
130 path = os.path.join(firmware_dir, "depthcharge", basename)
131 if not os.path.exists(path):
132 path = os.path.join(firmware_dir, basename)
133 if os.path.exists(path):
134 logging.warning(
135 "Auto-detected symbol file at %s... make sure that this"
136 " matches the image on your DUT!",
137 path,
138 )
139 return path
Julius Werner634ccac2014-06-24 13:59:18 -0700140
Alex Klein1699fab2022-09-08 08:46:06 -0600141 raise ValueError(
142 "Could not find depthcharge symbol file (dev.elf)! "
Julius Werner3b893e82023-03-06 18:33:37 -0800143 "(You can use -y to supply it manually.)"
Alex Klein1699fab2022-09-08 08:46:06 -0600144 )
Julius Werner634ccac2014-06-24 13:59:18 -0700145
146
147# TODO(jwerner): Fine tune |wait| delay or maybe even make it configurable if
148# this causes problems due to load on the host. The callers where this is
149# critical should all have their own timeouts now, though, so it's questionable
150# whether the delay here is even needed at all anymore.
151def ReadAll(fd, wait=0.03):
Alex Klein68b270c2023-04-14 14:42:50 -0600152 """Read from |fd| until no data has come for at least |wait| seconds."""
Alex Klein1699fab2022-09-08 08:46:06 -0600153 data = []
154 try:
155 while True:
156 time.sleep(wait)
157 new_data = os.read(fd, 4096)
158 if not new_data:
159 break
160 data.append(new_data)
161 except OSError as e:
162 if e.errno != errno.EAGAIN:
163 raise
164 data = b"".join(data)
165 if data:
166 logging.debug(data.decode("ascii", errors="replace"))
167 return data
Julius Werner634ccac2014-06-24 13:59:18 -0700168
169
170def GdbChecksum(message):
Alex Klein1699fab2022-09-08 08:46:06 -0600171 """Calculate a remote-GDB style checksum."""
172 chksum = sum(message)
173 return (b"%.2x" % chksum)[-2:]
Julius Werner634ccac2014-06-24 13:59:18 -0700174
175
176def TestConnection(fd):
Alex Klein68b270c2023-04-14 14:42:50 -0600177 """Return True if there's a responsive GDB stub on the other end of |fd|."""
Alex Klein1699fab2022-09-08 08:46:06 -0600178 cmd = b"vUnknownCommand"
179 for _ in range(3):
180 os.write(fd, b"$%s#%s\n" % (cmd, GdbChecksum(cmd)))
181 reply = ReadAll(fd)
182 if b"+$#00" in reply:
183 os.write(fd, b"+")
184 logging.info(
185 "TestConnection: Could successfully connect to remote end."
186 )
187 return True
188 logging.info("TestConnection: Remote end does not respond.")
189 return False
Julius Werner634ccac2014-06-24 13:59:18 -0700190
191
192def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600193 opts = ParseArgs(argv)
194 servo = client.ServoClient(host=opts.host, port=opts.port)
Julius Werner634ccac2014-06-24 13:59:18 -0700195
Alex Klein1699fab2022-09-08 08:46:06 -0600196 if not opts.tty:
197 try:
198 opts.tty = servo.get("cpu_uart_pty")
199 except (client.ServoClientError, socket.error):
200 logging.error(
201 "Cannot auto-detect TTY file without servod. Use the --tty "
202 "option."
203 )
204 raise
205 with terminal_freezer.TerminalFreezer(opts.tty):
206 fd = os.open(opts.tty, os.O_RDWR | os.O_NONBLOCK)
Julius Werner634ccac2014-06-24 13:59:18 -0700207
Alex Klein1699fab2022-09-08 08:46:06 -0600208 data = ReadAll(fd)
209 if opts.reboot == "auto":
210 if TestConnection(fd):
211 opts.reboot = "no"
212 else:
213 opts.reboot = "yes"
Julius Werner634ccac2014-06-24 13:59:18 -0700214
Alex Klein1699fab2022-09-08 08:46:06 -0600215 if opts.reboot == "yes":
216 logging.info("Rebooting DUT...")
217 try:
218 servo.set("warm_reset", "on")
219 time.sleep(0.1)
220 servo.set("warm_reset", "off")
221 except (client.ServoClientError, socket.error):
222 logging.error(
223 "Cannot reboot without a Servo board. You have to boot "
224 "into developer mode and press CTRL+G manually before "
225 "running fwgdb."
226 )
227 raise
Julius Werner634ccac2014-06-24 13:59:18 -0700228
Alex Klein68b270c2023-04-14 14:42:50 -0600229 # Throw away old data to avoid confusion from messages before the
230 # reboot.
Alex Klein1699fab2022-09-08 08:46:06 -0600231 data = b""
232 msg = "Could not reboot into depthcharge!"
233 with timeout_util.Timeout(10, msg):
234 while not re.search(_PTRN_BOARD, data):
235 data += ReadAll(fd)
Julius Werner634ccac2014-06-24 13:59:18 -0700236
Alex Klein1699fab2022-09-08 08:46:06 -0600237 msg = (
238 "Could not enter GDB mode with CTRL+G! "
Alex Klein68b270c2023-04-14 14:42:50 -0600239 '(Confirm that you flashed an "image.dev.bin" image to this '
240 "DUT, and that you have GBB_FLAG_FORCE_DEV_SWITCH_ON (0x8) "
241 "set.)"
Alex Klein1699fab2022-09-08 08:46:06 -0600242 )
243 with timeout_util.Timeout(5, msg):
244 while not re.search(_PTRN_GDB, data):
Alex Klein68b270c2023-04-14 14:42:50 -0600245 # Some delay to avoid spamming the console too hard while
246 # not being long enough to cause a user-visible slowdown.
Alex Klein1699fab2022-09-08 08:46:06 -0600247 time.sleep(0.5)
248 # Send a CTRL+G to tell depthcharge to trap into GDB.
249 logging.debug("[Ctrl+G]")
250 os.write(fd, b"\x07")
251 data += ReadAll(fd)
Julius Werner634ccac2014-06-24 13:59:18 -0700252
Alex Klein1699fab2022-09-08 08:46:06 -0600253 if not opts.board:
254 matches = re.findall(_PTRN_BOARD, data)
255 if not matches:
256 raise ValueError(
257 "Could not auto-detect board! Please use -b option."
258 )
Julius Werner3b893e82023-03-06 18:33:37 -0800259 opts.board = matches[-1].decode().lower()
Alex Klein1699fab2022-09-08 08:46:06 -0600260 logging.info(
261 "Auto-detected board as %s from DUT console output.", opts.board
262 )
Julius Werner634ccac2014-06-24 13:59:18 -0700263
Alex Klein1699fab2022-09-08 08:46:06 -0600264 if not TestConnection(fd):
265 raise IOError(
266 "Could not connect to remote end! Confirm that your DUT is "
267 "running in GDB mode on %s." % opts.tty
268 )
Julius Werner634ccac2014-06-24 13:59:18 -0700269
Alex Klein68b270c2023-04-14 14:42:50 -0600270 # Eat up leftover data or it will spill back to terminal.
Alex Klein1699fab2022-09-08 08:46:06 -0600271 ReadAll(fd)
272 os.close(fd)
Julius Werner634ccac2014-06-24 13:59:18 -0700273
Alex Klein1699fab2022-09-08 08:46:06 -0600274 opts.execute.insert(0, "target remote %s" % opts.tty)
275 ex_args = sum([["--ex", cmd] for cmd in opts.execute], [])
Julius Werner634ccac2014-06-24 13:59:18 -0700276
Alex Klein1699fab2022-09-08 08:46:06 -0600277 elf = FindSymbols(opts.symbols, opts.board)
278 gdb_cmd = GetGdbForElf(elf)
Raul E Rangel72f32712018-05-29 11:42:59 -0600279
Alex Klein1699fab2022-09-08 08:46:06 -0600280 gdb_args = [
281 "--symbols",
282 elf,
283 "--directory",
284 _SRC_DC,
285 "--directory",
286 _SRC_VB,
287 "--directory",
288 _SRC_LP,
289 ] + ex_args
Raul E Rangel72f32712018-05-29 11:42:59 -0600290
Alex Klein1699fab2022-09-08 08:46:06 -0600291 if opts.cgdb:
292 full_cmd = ["cgdb", "-d", gdb_cmd, "--"] + gdb_args
293 else:
294 full_cmd = [gdb_cmd] + gdb_args
Raul E Rangel72f32712018-05-29 11:42:59 -0600295
Alex Klein1699fab2022-09-08 08:46:06 -0600296 logging.info("Launching GDB...")
297 cros_build_lib.run(
298 full_cmd, ignore_sigint=True, debug_level=logging.WARNING
299 )