blob: d78e35e942d4473dc902d1553cfb498f9ce5b0a0 [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
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 -y to specify manually"
118 % (", ".join(unified_build_dirs))
119 )
Raul E Rangeleb8aadc2018-05-17 09:10:27 -0600120
Alex Klein1699fab2022-09-08 08:46:06 -0600121 if not firmware_dir:
122 firmware_dir = os.path.join(
123 build_target_lib.get_default_sysroot_path(board), "firmware"
124 )
Julius Werner634ccac2014-06-24 13:59:18 -0700125
Alex Klein1699fab2022-09-08 08:46:06 -0600126 # Very old firmware you might still find on GoldenEye had dev.ro.elf.
127 basenames = ["dev.elf", "dev.ro.elf"]
128 for basename in basenames:
129 path = os.path.join(firmware_dir, "depthcharge", basename)
130 if not os.path.exists(path):
131 path = os.path.join(firmware_dir, basename)
132 if os.path.exists(path):
133 logging.warning(
134 "Auto-detected symbol file at %s... make sure that this"
135 " matches the image on your DUT!",
136 path,
137 )
138 return path
Julius Werner634ccac2014-06-24 13:59:18 -0700139
Alex Klein1699fab2022-09-08 08:46:06 -0600140 raise ValueError(
141 "Could not find depthcharge symbol file (dev.elf)! "
Julius Werner3b893e82023-03-06 18:33:37 -0800142 "(You can use -y to supply it manually.)"
Alex Klein1699fab2022-09-08 08:46:06 -0600143 )
Julius Werner634ccac2014-06-24 13:59:18 -0700144
145
146# TODO(jwerner): Fine tune |wait| delay or maybe even make it configurable if
147# this causes problems due to load on the host. The callers where this is
148# critical should all have their own timeouts now, though, so it's questionable
149# whether the delay here is even needed at all anymore.
150def ReadAll(fd, wait=0.03):
Alex Klein1699fab2022-09-08 08:46:06 -0600151 """Read from |fd| until no more data has come for at least |wait| seconds."""
152 data = []
153 try:
154 while True:
155 time.sleep(wait)
156 new_data = os.read(fd, 4096)
157 if not new_data:
158 break
159 data.append(new_data)
160 except OSError as e:
161 if e.errno != errno.EAGAIN:
162 raise
163 data = b"".join(data)
164 if data:
165 logging.debug(data.decode("ascii", errors="replace"))
166 return data
Julius Werner634ccac2014-06-24 13:59:18 -0700167
168
169def GdbChecksum(message):
Alex Klein1699fab2022-09-08 08:46:06 -0600170 """Calculate a remote-GDB style checksum."""
171 chksum = sum(message)
172 return (b"%.2x" % chksum)[-2:]
Julius Werner634ccac2014-06-24 13:59:18 -0700173
174
175def TestConnection(fd):
Alex Klein1699fab2022-09-08 08:46:06 -0600176 """Return True iff there is a resposive GDB stub on the other end of 'fd'."""
177 cmd = b"vUnknownCommand"
178 for _ in range(3):
179 os.write(fd, b"$%s#%s\n" % (cmd, GdbChecksum(cmd)))
180 reply = ReadAll(fd)
181 if b"+$#00" in reply:
182 os.write(fd, b"+")
183 logging.info(
184 "TestConnection: Could successfully connect to remote end."
185 )
186 return True
187 logging.info("TestConnection: Remote end does not respond.")
188 return False
Julius Werner634ccac2014-06-24 13:59:18 -0700189
190
191def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600192 opts = ParseArgs(argv)
193 servo = client.ServoClient(host=opts.host, port=opts.port)
Julius Werner634ccac2014-06-24 13:59:18 -0700194
Alex Klein1699fab2022-09-08 08:46:06 -0600195 if not opts.tty:
196 try:
197 opts.tty = servo.get("cpu_uart_pty")
198 except (client.ServoClientError, socket.error):
199 logging.error(
200 "Cannot auto-detect TTY file without servod. Use the --tty "
201 "option."
202 )
203 raise
204 with terminal_freezer.TerminalFreezer(opts.tty):
205 fd = os.open(opts.tty, os.O_RDWR | os.O_NONBLOCK)
Julius Werner634ccac2014-06-24 13:59:18 -0700206
Alex Klein1699fab2022-09-08 08:46:06 -0600207 data = ReadAll(fd)
208 if opts.reboot == "auto":
209 if TestConnection(fd):
210 opts.reboot = "no"
211 else:
212 opts.reboot = "yes"
Julius Werner634ccac2014-06-24 13:59:18 -0700213
Alex Klein1699fab2022-09-08 08:46:06 -0600214 if opts.reboot == "yes":
215 logging.info("Rebooting DUT...")
216 try:
217 servo.set("warm_reset", "on")
218 time.sleep(0.1)
219 servo.set("warm_reset", "off")
220 except (client.ServoClientError, socket.error):
221 logging.error(
222 "Cannot reboot without a Servo board. You have to boot "
223 "into developer mode and press CTRL+G manually before "
224 "running fwgdb."
225 )
226 raise
Julius Werner634ccac2014-06-24 13:59:18 -0700227
Alex Klein1699fab2022-09-08 08:46:06 -0600228 # Throw away old data to avoid confusion from messages before the reboot
229 data = b""
230 msg = "Could not reboot into depthcharge!"
231 with timeout_util.Timeout(10, msg):
232 while not re.search(_PTRN_BOARD, data):
233 data += ReadAll(fd)
Julius Werner634ccac2014-06-24 13:59:18 -0700234
Alex Klein1699fab2022-09-08 08:46:06 -0600235 msg = (
236 "Could not enter GDB mode with CTRL+G! "
237 '(Confirm that you flashed an "image.dev.bin" image to this DUT, '
238 "and that you have GBB_FLAG_FORCE_DEV_SWITCH_ON (0x8) set.)"
239 )
240 with timeout_util.Timeout(5, msg):
241 while not re.search(_PTRN_GDB, data):
242 # Some delay to avoid spamming the console too hard while not being
243 # long enough to cause a user-visible slowdown.
244 time.sleep(0.5)
245 # Send a CTRL+G to tell depthcharge to trap into GDB.
246 logging.debug("[Ctrl+G]")
247 os.write(fd, b"\x07")
248 data += ReadAll(fd)
Julius Werner634ccac2014-06-24 13:59:18 -0700249
Alex Klein1699fab2022-09-08 08:46:06 -0600250 if not opts.board:
251 matches = re.findall(_PTRN_BOARD, data)
252 if not matches:
253 raise ValueError(
254 "Could not auto-detect board! Please use -b option."
255 )
Julius Werner3b893e82023-03-06 18:33:37 -0800256 opts.board = matches[-1].decode().lower()
Alex Klein1699fab2022-09-08 08:46:06 -0600257 logging.info(
258 "Auto-detected board as %s from DUT console output.", opts.board
259 )
Julius Werner634ccac2014-06-24 13:59:18 -0700260
Alex Klein1699fab2022-09-08 08:46:06 -0600261 if not TestConnection(fd):
262 raise IOError(
263 "Could not connect to remote end! Confirm that your DUT is "
264 "running in GDB mode on %s." % opts.tty
265 )
Julius Werner634ccac2014-06-24 13:59:18 -0700266
Alex Klein1699fab2022-09-08 08:46:06 -0600267 # Eat up leftover data or it will spill back to terminal
268 ReadAll(fd)
269 os.close(fd)
Julius Werner634ccac2014-06-24 13:59:18 -0700270
Alex Klein1699fab2022-09-08 08:46:06 -0600271 opts.execute.insert(0, "target remote %s" % opts.tty)
272 ex_args = sum([["--ex", cmd] for cmd in opts.execute], [])
Julius Werner634ccac2014-06-24 13:59:18 -0700273
Alex Klein1699fab2022-09-08 08:46:06 -0600274 elf = FindSymbols(opts.symbols, opts.board)
275 gdb_cmd = GetGdbForElf(elf)
Raul E Rangel72f32712018-05-29 11:42:59 -0600276
Alex Klein1699fab2022-09-08 08:46:06 -0600277 gdb_args = [
278 "--symbols",
279 elf,
280 "--directory",
281 _SRC_DC,
282 "--directory",
283 _SRC_VB,
284 "--directory",
285 _SRC_LP,
286 ] + ex_args
Raul E Rangel72f32712018-05-29 11:42:59 -0600287
Alex Klein1699fab2022-09-08 08:46:06 -0600288 if opts.cgdb:
289 full_cmd = ["cgdb", "-d", gdb_cmd, "--"] + gdb_args
290 else:
291 full_cmd = [gdb_cmd] + gdb_args
Raul E Rangel72f32712018-05-29 11:42:59 -0600292
Alex Klein1699fab2022-09-08 08:46:06 -0600293 logging.info("Launching GDB...")
294 cros_build_lib.run(
295 full_cmd, ignore_sigint=True, debug_level=logging.WARNING
296 )