Upstream version 8.36.161.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / scripts / cros_generate_breakpad_symbols.py
1 # Copyright (c) 2013 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 """Generate minidump symbols for use by the Crash server.
6
7 Note: This should be run inside the chroot.
8
9 This produces files in the breakpad format required by minidump_stackwalk and
10 the crash server to dump stack information.
11
12 Basically it scans all the split .debug files in /build/$BOARD/usr/lib/debug/
13 and converts them over using the `dump_syms` programs.  Those plain text .sym
14 files are then stored in /build/$BOARD/usr/lib/debug/breakpad/.
15
16 If you want to actually upload things, see upload_symbols.py.
17 """
18
19 import collections
20 import ctypes
21 import logging
22 import multiprocessing
23 import os
24 import tempfile
25
26 from chromite.lib import commandline
27 from chromite.lib import cros_build_lib
28 from chromite.lib import osutils
29 from chromite.lib import parallel
30
31
32 SymbolHeader = collections.namedtuple('SymbolHeader',
33                                       ('cpu', 'id', 'name', 'os',))
34
35
36 def ReadSymsHeader(sym_file):
37   """Parse the header of the symbol file
38
39   The first line of the syms file will read like:
40     MODULE Linux arm F4F6FA6CCBDEF455039C8DE869C8A2F40 blkid
41
42   https://code.google.com/p/google-breakpad/wiki/SymbolFiles
43
44   Args:
45     sym_file: The symbol file to parse
46
47   Returns:
48     A SymbolHeader object
49
50   Raises:
51     ValueError if the first line of |sym_file| is invalid
52   """
53   with cros_build_lib.Open(sym_file) as f:
54     header = f.readline().split()
55
56   if header[0] != 'MODULE' or len(header) != 5:
57     raise ValueError('header of sym file is invalid')
58
59   return SymbolHeader(os=header[1], cpu=header[2], id=header[3], name=header[4])
60
61
62 def GenerateBreakpadSymbol(elf_file, debug_file=None, breakpad_dir=None,
63                            board=None, strip_cfi=False, num_errors=None):
64   """Generate the symbols for |elf_file| using |debug_file|
65
66   Args:
67     elf_file: The file to dump symbols for
68     debug_file: Split debug file to use for symbol information
69     breakpad_dir: The dir to store the output symbol file in
70     board: If |breakpad_dir| is not specified, use |board| to find it
71     strip_cfi: Do not generate CFI data
72     num_errors: An object to update with the error count (needs a .value member)
73
74   Returns:
75     The number of errors that were encountered.
76   """
77   if breakpad_dir is None:
78     breakpad_dir = FindBreakpadDir(board)
79   if num_errors is None:
80     num_errors = ctypes.c_int()
81
82   cmd_base = ['dump_syms']
83   if strip_cfi:
84     cmd_base += ['-c']
85   # Some files will not be readable by non-root (e.g. set*id /bin/su).
86   needs_sudo = not os.access(elf_file, os.R_OK)
87
88   def _DumpIt(cmd_args):
89     if needs_sudo:
90       run_command = cros_build_lib.SudoRunCommand
91     else:
92       run_command = cros_build_lib.RunCommand
93     return run_command(
94         cmd_base + cmd_args, redirect_stderr=True, log_stdout_to_file=temp.name,
95         error_code_ok=True, debug_level=logging.DEBUG)
96
97   def _CrashCheck(ret, msg):
98     if ret < 0:
99       cros_build_lib.PrintBuildbotStepWarnings()
100       cros_build_lib.Warning('dump_syms crashed with %s; %s',
101                              osutils.StrSignal(-ret), msg)
102
103   osutils.SafeMakedirs(breakpad_dir)
104   with tempfile.NamedTemporaryFile(dir=breakpad_dir, bufsize=0) as temp:
105     if debug_file:
106       # Try to dump the symbols using the debug file like normal.
107       cmd_args = [elf_file, os.path.dirname(debug_file)]
108       result = _DumpIt(cmd_args)
109
110       if result.returncode:
111         # Sometimes dump_syms can crash because there's too much info.
112         # Try dumping and stripping the extended stuff out.  At least
113         # this way we'll get the extended symbols.  http://crbug.com/266064
114         _CrashCheck(result.returncode, 'retrying w/out CFI')
115         cmd_args = ['-c', '-r'] + cmd_args
116         result = _DumpIt(cmd_args)
117         _CrashCheck(result.returncode, 'retrying w/out debug')
118
119       basic_dump = result.returncode
120     else:
121       basic_dump = True
122
123     if basic_dump:
124       # If that didn't work (no debug, or dump_syms still failed), try
125       # dumping just the file itself directly.
126       result = _DumpIt([elf_file])
127       if result.returncode:
128         # A lot of files (like kernel files) contain no debug information,
129         # do not consider such occurrences as errors.
130         cros_build_lib.PrintBuildbotStepWarnings()
131         _CrashCheck(result.returncode, 'giving up entirely')
132         if 'file contains no debugging information' in result.error:
133           cros_build_lib.Warning('no symbols found for %s', elf_file)
134         else:
135           num_errors.value += 1
136           cros_build_lib.Error('dumping symbols for %s failed:\n%s',
137                                elf_file, result.error)
138         return num_errors.value
139
140     # Move the dumped symbol file to the right place:
141     # /build/$BOARD/usr/lib/debug/breakpad/<module-name>/<id>/<module-name>.sym
142     header = ReadSymsHeader(temp)
143     cros_build_lib.Info('Dumped %s as %s : %s', elf_file, header.name,
144                         header.id)
145     sym_file = os.path.join(breakpad_dir, header.name, header.id,
146                             header.name + '.sym')
147     osutils.SafeMakedirs(os.path.dirname(sym_file))
148     os.rename(temp.name, sym_file)
149     os.chmod(sym_file, 0o644)
150     temp.delete = False
151
152   return num_errors.value
153
154
155 def GenerateBreakpadSymbols(board, breakpad_dir=None, strip_cfi=False,
156                             generate_count=None, sysroot=None,
157                             num_processes=None, clean_breakpad=False,
158                             exclude_dirs=(), file_list=None):
159   """Generate symbols for this board.
160
161   If |file_list| is None, symbols are generated for all executables, otherwise
162   only for the files included in |file_list|.
163
164   TODO(build):
165   This should be merged with buildbot_commands.GenerateBreakpadSymbols()
166   once we rewrite cros_generate_breakpad_symbols in python.
167
168   Args:
169     board: The board whose symbols we wish to generate
170     breakpad_dir: The full path to the breakpad directory where symbols live
171     strip_cfi: Do not generate CFI data
172     generate_count: If set, only generate this many symbols (meant for testing)
173     sysroot: The root where to find the corresponding ELFs
174     num_processes: Number of jobs to run in parallel
175     clean_breakpad: Should we `rm -rf` the breakpad output dir first; note: we
176       do not do any locking, so do not run more than one in parallel when True
177     exclude_dirs: List of dirs (relative to |sysroot|) to not search
178     file_list: Only generate symbols for files in this list. Each file must be a
179       full path (including |sysroot| prefix).
180       TODO(build): Support paths w/o |sysroot|.
181
182   Returns:
183     The number of errors that were encountered.
184   """
185   if breakpad_dir is None:
186     breakpad_dir = FindBreakpadDir(board)
187   if sysroot is None:
188     sysroot = cros_build_lib.GetSysroot(board=board)
189   if clean_breakpad:
190     cros_build_lib.Info('cleaning out %s first', breakpad_dir)
191     osutils.RmDir(breakpad_dir, ignore_missing=True, sudo=True)
192   # Make sure non-root can write out symbols as needed.
193   osutils.SafeMakedirs(breakpad_dir, sudo=True)
194   if not os.access(breakpad_dir, os.W_OK):
195     cros_build_lib.SudoRunCommand(['chown', '-R', str(os.getuid()),
196                                    breakpad_dir])
197   debug_dir = FindDebugDir(board)
198   exclude_paths = [os.path.join(debug_dir, x) for x in exclude_dirs]
199   if file_list is None:
200     file_list = []
201   file_filter = dict.fromkeys([os.path.normpath(x) for x in file_list], False)
202
203   cros_build_lib.Info('generating breakpad symbols using %s', debug_dir)
204
205   # Let's locate all the debug_files and elfs first along with the debug file
206   # sizes.  This way we can start processing the largest files first in parallel
207   # with the small ones.
208   # If |file_list| was given, ignore all other files.
209   targets = []
210   for root, dirs, files in os.walk(debug_dir):
211     if root in exclude_paths:
212       cros_build_lib.Info('Skipping excluded dir %s', root)
213       del dirs[:]
214       continue
215
216     for debug_file in files:
217       debug_file = os.path.join(root, debug_file)
218       # Turn /build/$BOARD/usr/lib/debug/sbin/foo.debug into
219       # /build/$BOARD/sbin/foo.
220       elf_file = os.path.join(sysroot, debug_file[len(debug_dir) + 1:-6])
221
222       if file_filter:
223         if elf_file in file_filter:
224           file_filter[elf_file] = True
225         elif debug_file in file_filter:
226           file_filter[debug_file] = True
227         else:
228           continue
229
230       # Filter out files based on common issues with the debug file.
231       if not debug_file.endswith('.debug'):
232         continue
233
234       elif debug_file.endswith('.ko.debug'):
235         cros_build_lib.Debug('Skipping kernel module %s', debug_file)
236         continue
237
238       elif os.path.islink(debug_file):
239         # The build-id stuff is common enough to filter out by default.
240         if '/.build-id/' in debug_file:
241           msg = cros_build_lib.Debug
242         else:
243           msg = cros_build_lib.Warning
244         msg('Skipping symbolic link %s', debug_file)
245         continue
246
247       # Filter out files based on common issues with the elf file.
248       if not os.path.exists(elf_file):
249         # Sometimes we filter out programs from /usr/bin but leave behind
250         # the .debug file.
251         cros_build_lib.Warning('Skipping missing %s', elf_file)
252         continue
253
254       targets.append((os.path.getsize(debug_file), elf_file, debug_file))
255
256   bg_errors = multiprocessing.Value('i')
257   if file_filter:
258     files_not_found = [x for x, found in file_filter.iteritems() if not found]
259     bg_errors.value += len(files_not_found)
260     if files_not_found:
261       cros_build_lib.Error('Failed to find requested files: %s',
262                            files_not_found)
263
264   # Now start generating symbols for the discovered elfs.
265   with parallel.BackgroundTaskRunner(GenerateBreakpadSymbol,
266                                      breakpad_dir=breakpad_dir, board=board,
267                                      strip_cfi=strip_cfi,
268                                      num_errors=bg_errors,
269                                      processes=num_processes) as queue:
270     for _, elf_file, debug_file in sorted(targets, reverse=True):
271       if generate_count == 0:
272         break
273
274       queue.put([elf_file, debug_file])
275       if generate_count is not None:
276         generate_count -= 1
277         if generate_count == 0:
278           break
279
280   return bg_errors.value
281
282
283 def FindDebugDir(board):
284   """Given a |board|, return the path to the split debug dir for it"""
285   sysroot = cros_build_lib.GetSysroot(board=board)
286   return os.path.join(sysroot, 'usr', 'lib', 'debug')
287
288
289 def FindBreakpadDir(board):
290   """Given a |board|, return the path to the breakpad dir for it"""
291   return os.path.join(FindDebugDir(board), 'breakpad')
292
293
294 def main(argv):
295   parser = commandline.ArgumentParser(description=__doc__)
296
297   parser.add_argument('--board', default=None,
298                       help='board to generate symbols for')
299   parser.add_argument('--breakpad_root', type='path', default=None,
300                       help='root directory for breakpad symbols')
301   parser.add_argument('--exclude-dir', type=str, action='append',
302                       default=[],
303                       help='directory (relative to |board| root) to not search')
304   parser.add_argument('--generate-count', type=int, default=None,
305                       help='only generate # number of symbols')
306   parser.add_argument('--noclean', dest='clean', action='store_false',
307                       default=True,
308                       help='do not clean out breakpad dir before running')
309   parser.add_argument('--jobs', type=int, default=None,
310                       help='limit number of parallel jobs')
311   parser.add_argument('--strip_cfi', action='store_true', default=False,
312                       help='do not generate CFI data (pass -c to dump_syms)')
313   parser.add_argument('file_list', nargs='*', default=None,
314                       help='generate symbols for only these files '
315                            '(e.g. /build/$BOARD/usr/bin/foo)')
316
317   opts = parser.parse_args(argv)
318   opts.Freeze()
319
320   if opts.board is None:
321     cros_build_lib.Die('--board is required')
322
323   ret = GenerateBreakpadSymbols(opts.board, breakpad_dir=opts.breakpad_root,
324                                 strip_cfi=opts.strip_cfi,
325                                 generate_count=opts.generate_count,
326                                 num_processes=opts.jobs,
327                                 clean_breakpad=opts.clean,
328                                 exclude_dirs=opts.exclude_dir,
329                                 file_list=opts.file_list)
330   if ret:
331     cros_build_lib.Error('encountered %i problem(s)', ret)
332     # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
333     # return 0 in case we are a multiple of the mask.
334     ret = 1
335
336   return ret