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