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