blob: e24bff7d5d6440b5e38e3dbba274d09e1f975601 [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
Mike Frysinger69cb41d2013-08-11 20:08:19 -04002# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Generate minidump symbols for use by the Crash server.
7
8Note: This should be run inside the chroot.
9
10This produces files in the breakpad format required by minidump_stackwalk and
11the crash server to dump stack information.
12
13Basically it scans all the split .debug files in /build/$BOARD/usr/lib/debug/
14and converts them over using the `dump_syms` programs. Those plain text .sym
15files are then stored in /build/$BOARD/usr/lib/debug/breakpad/.
16
Mike Frysinger02e1e072013-11-10 22:11:34 -050017If you want to actually upload things, see upload_symbols.py.
18"""
Mike Frysinger69cb41d2013-08-11 20:08:19 -040019
Mike Frysinger383367e2014-09-16 15:06:17 -040020from __future__ import print_function
21
Mike Frysinger69cb41d2013-08-11 20:08:19 -040022import collections
23import ctypes
Mike Frysinger69cb41d2013-08-11 20:08:19 -040024import multiprocessing
25import os
26import tempfile
27
28from chromite.lib import commandline
29from chromite.lib import cros_build_lib
Ralph Nathan91874ca2015-03-19 13:29:41 -070030from chromite.lib import cros_logging as logging
Mike Frysinger69cb41d2013-08-11 20:08:19 -040031from chromite.lib import osutils
32from chromite.lib import parallel
Mike Frysinger96ad3f22014-04-24 23:27:27 -040033from chromite.lib import signals
Mike Frysinger69cb41d2013-08-11 20:08:19 -040034
35
36SymbolHeader = collections.namedtuple('SymbolHeader',
37 ('cpu', 'id', 'name', 'os',))
38
39
40def ReadSymsHeader(sym_file):
41 """Parse the header of the symbol file
42
43 The first line of the syms file will read like:
44 MODULE Linux arm F4F6FA6CCBDEF455039C8DE869C8A2F40 blkid
45
46 https://code.google.com/p/google-breakpad/wiki/SymbolFiles
47
48 Args:
49 sym_file: The symbol file to parse
Mike Frysinger1a736a82013-12-12 01:50:59 -050050
Mike Frysinger69cb41d2013-08-11 20:08:19 -040051 Returns:
52 A SymbolHeader object
Mike Frysinger1a736a82013-12-12 01:50:59 -050053
Mike Frysinger69cb41d2013-08-11 20:08:19 -040054 Raises:
55 ValueError if the first line of |sym_file| is invalid
56 """
Mike Frysinger50cedd32014-02-09 23:03:18 -050057 with cros_build_lib.Open(sym_file) as f:
58 header = f.readline().split()
Mike Frysinger69cb41d2013-08-11 20:08:19 -040059
60 if header[0] != 'MODULE' or len(header) != 5:
61 raise ValueError('header of sym file is invalid')
Mike Frysinger50cedd32014-02-09 23:03:18 -050062
Mike Frysinger69cb41d2013-08-11 20:08:19 -040063 return SymbolHeader(os=header[1], cpu=header[2], id=header[3], name=header[4])
64
65
66def GenerateBreakpadSymbol(elf_file, debug_file=None, breakpad_dir=None,
Don Garrett39f0dc62015-09-24 15:18:31 -070067 strip_cfi=False, num_errors=None,
68 dump_syms_cmd='dump_syms'):
Mike Frysinger69cb41d2013-08-11 20:08:19 -040069 """Generate the symbols for |elf_file| using |debug_file|
70
71 Args:
72 elf_file: The file to dump symbols for
73 debug_file: Split debug file to use for symbol information
74 breakpad_dir: The dir to store the output symbol file in
Mike Frysinger69cb41d2013-08-11 20:08:19 -040075 strip_cfi: Do not generate CFI data
76 num_errors: An object to update with the error count (needs a .value member)
Don Garrett39f0dc62015-09-24 15:18:31 -070077 dump_syms_cmd: Command to use for dumping symbols.
Mike Frysinger1a736a82013-12-12 01:50:59 -050078
Mike Frysinger69cb41d2013-08-11 20:08:19 -040079 Returns:
Don Garrettc9de3ac2015-10-01 15:40:10 -070080 The name of symbol file written out.
Mike Frysinger69cb41d2013-08-11 20:08:19 -040081 """
Don Garrette548cff2015-09-23 14:36:21 -070082 assert breakpad_dir
Mike Frysinger69cb41d2013-08-11 20:08:19 -040083 if num_errors is None:
84 num_errors = ctypes.c_int()
85
Mike Frysinger2808deb2016-01-28 01:22:13 -050086 cmd_base = [dump_syms_cmd, '-v']
Mike Frysinger69cb41d2013-08-11 20:08:19 -040087 if strip_cfi:
88 cmd_base += ['-c']
89 # Some files will not be readable by non-root (e.g. set*id /bin/su).
90 needs_sudo = not os.access(elf_file, os.R_OK)
91
92 def _DumpIt(cmd_args):
93 if needs_sudo:
94 run_command = cros_build_lib.SudoRunCommand
95 else:
96 run_command = cros_build_lib.RunCommand
97 return run_command(
98 cmd_base + cmd_args, redirect_stderr=True, log_stdout_to_file=temp.name,
99 error_code_ok=True, debug_level=logging.DEBUG)
100
101 def _CrashCheck(ret, msg):
102 if ret < 0:
Prathmesh Prabhu17f07422015-07-17 11:40:40 -0700103 logging.PrintBuildbotStepWarnings()
Ralph Nathan446aee92015-03-23 14:44:56 -0700104 logging.warning('dump_syms crashed with %s; %s',
105 signals.StrSignal(-ret), msg)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400106
107 osutils.SafeMakedirs(breakpad_dir)
108 with tempfile.NamedTemporaryFile(dir=breakpad_dir, bufsize=0) as temp:
109 if debug_file:
110 # Try to dump the symbols using the debug file like normal.
111 cmd_args = [elf_file, os.path.dirname(debug_file)]
112 result = _DumpIt(cmd_args)
113
114 if result.returncode:
115 # Sometimes dump_syms can crash because there's too much info.
116 # Try dumping and stripping the extended stuff out. At least
117 # this way we'll get the extended symbols. http://crbug.com/266064
118 _CrashCheck(result.returncode, 'retrying w/out CFI')
119 cmd_args = ['-c', '-r'] + cmd_args
120 result = _DumpIt(cmd_args)
121 _CrashCheck(result.returncode, 'retrying w/out debug')
122
123 basic_dump = result.returncode
124 else:
125 basic_dump = True
126
127 if basic_dump:
128 # If that didn't work (no debug, or dump_syms still failed), try
129 # dumping just the file itself directly.
130 result = _DumpIt([elf_file])
131 if result.returncode:
132 # A lot of files (like kernel files) contain no debug information,
133 # do not consider such occurrences as errors.
Prathmesh Prabhu17f07422015-07-17 11:40:40 -0700134 logging.PrintBuildbotStepWarnings()
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400135 _CrashCheck(result.returncode, 'giving up entirely')
136 if 'file contains no debugging information' in result.error:
Ralph Nathan446aee92015-03-23 14:44:56 -0700137 logging.warning('no symbols found for %s', elf_file)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400138 else:
139 num_errors.value += 1
Ralph Nathan59900422015-03-24 10:41:17 -0700140 logging.error('dumping symbols for %s failed:\n%s', elf_file,
141 result.error)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400142 return num_errors.value
143
144 # Move the dumped symbol file to the right place:
145 # /build/$BOARD/usr/lib/debug/breakpad/<module-name>/<id>/<module-name>.sym
146 header = ReadSymsHeader(temp)
Ralph Nathan03047282015-03-23 11:09:32 -0700147 logging.info('Dumped %s as %s : %s', elf_file, header.name, header.id)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400148 sym_file = os.path.join(breakpad_dir, header.name, header.id,
149 header.name + '.sym')
150 osutils.SafeMakedirs(os.path.dirname(sym_file))
151 os.rename(temp.name, sym_file)
Mike Frysinger60ec1012013-10-21 00:11:10 -0400152 os.chmod(sym_file, 0o644)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400153 temp.delete = False
154
Don Garrettc9de3ac2015-10-01 15:40:10 -0700155 return sym_file
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400156
157
158def GenerateBreakpadSymbols(board, breakpad_dir=None, strip_cfi=False,
Mike Frysingeref9ab2f2013-08-26 22:16:00 -0400159 generate_count=None, sysroot=None,
Shawn Nematbakhsh2c169cb2013-10-29 16:23:58 -0700160 num_processes=None, clean_breakpad=False,
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700161 exclude_dirs=(), file_list=None):
162 """Generate symbols for this board.
163
164 If |file_list| is None, symbols are generated for all executables, otherwise
165 only for the files included in |file_list|.
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400166
167 TODO(build):
168 This should be merged with buildbot_commands.GenerateBreakpadSymbols()
169 once we rewrite cros_generate_breakpad_symbols in python.
170
171 Args:
172 board: The board whose symbols we wish to generate
173 breakpad_dir: The full path to the breakpad directory where symbols live
174 strip_cfi: Do not generate CFI data
175 generate_count: If set, only generate this many symbols (meant for testing)
176 sysroot: The root where to find the corresponding ELFs
Mike Frysingeref9ab2f2013-08-26 22:16:00 -0400177 num_processes: Number of jobs to run in parallel
Mike Frysinger9a628bb2013-10-24 15:51:37 -0400178 clean_breakpad: Should we `rm -rf` the breakpad output dir first; note: we
179 do not do any locking, so do not run more than one in parallel when True
Shawn Nematbakhsh2c169cb2013-10-29 16:23:58 -0700180 exclude_dirs: List of dirs (relative to |sysroot|) to not search
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700181 file_list: Only generate symbols for files in this list. Each file must be a
182 full path (including |sysroot| prefix).
183 TODO(build): Support paths w/o |sysroot|.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500184
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400185 Returns:
186 The number of errors that were encountered.
187 """
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400188 if sysroot is None:
Yu-Ju Hongdd9bb2b2014-01-03 17:08:26 -0800189 sysroot = cros_build_lib.GetSysroot(board=board)
Mike Frysinger3f571af2016-08-31 23:56:53 -0400190 if breakpad_dir is None:
191 breakpad_dir = FindBreakpadDir(board, sysroot=sysroot)
Mike Frysinger9a628bb2013-10-24 15:51:37 -0400192 if clean_breakpad:
Ralph Nathan03047282015-03-23 11:09:32 -0700193 logging.info('cleaning out %s first', breakpad_dir)
Mike Frysinger9a628bb2013-10-24 15:51:37 -0400194 osutils.RmDir(breakpad_dir, ignore_missing=True, sudo=True)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400195 # Make sure non-root can write out symbols as needed.
196 osutils.SafeMakedirs(breakpad_dir, sudo=True)
197 if not os.access(breakpad_dir, os.W_OK):
198 cros_build_lib.SudoRunCommand(['chown', '-R', str(os.getuid()),
199 breakpad_dir])
Mike Frysinger3f571af2016-08-31 23:56:53 -0400200 debug_dir = FindDebugDir(board, sysroot=sysroot)
Shawn Nematbakhsh2c169cb2013-10-29 16:23:58 -0700201 exclude_paths = [os.path.join(debug_dir, x) for x in exclude_dirs]
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700202 if file_list is None:
203 file_list = []
204 file_filter = dict.fromkeys([os.path.normpath(x) for x in file_list], False)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400205
Ralph Nathan03047282015-03-23 11:09:32 -0700206 logging.info('generating breakpad symbols using %s', debug_dir)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400207
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700208 # Let's locate all the debug_files and elfs first along with the debug file
209 # sizes. This way we can start processing the largest files first in parallel
210 # with the small ones.
211 # If |file_list| was given, ignore all other files.
212 targets = []
Shawn Nematbakhsh2c169cb2013-10-29 16:23:58 -0700213 for root, dirs, files in os.walk(debug_dir):
214 if root in exclude_paths:
Ralph Nathan03047282015-03-23 11:09:32 -0700215 logging.info('Skipping excluded dir %s', root)
Shawn Nematbakhsh2c169cb2013-10-29 16:23:58 -0700216 del dirs[:]
217 continue
218
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400219 for debug_file in files:
220 debug_file = os.path.join(root, debug_file)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400221 # Turn /build/$BOARD/usr/lib/debug/sbin/foo.debug into
222 # /build/$BOARD/sbin/foo.
223 elf_file = os.path.join(sysroot, debug_file[len(debug_dir) + 1:-6])
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700224
225 if file_filter:
226 if elf_file in file_filter:
227 file_filter[elf_file] = True
228 elif debug_file in file_filter:
229 file_filter[debug_file] = True
230 else:
231 continue
232
233 # Filter out files based on common issues with the debug file.
234 if not debug_file.endswith('.debug'):
235 continue
236
237 elif debug_file.endswith('.ko.debug'):
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700238 logging.debug('Skipping kernel module %s', debug_file)
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700239 continue
240
241 elif os.path.islink(debug_file):
242 # The build-id stuff is common enough to filter out by default.
243 if '/.build-id/' in debug_file:
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700244 msg = logging.debug
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700245 else:
Ralph Nathan446aee92015-03-23 14:44:56 -0700246 msg = logging.warning
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700247 msg('Skipping symbolic link %s', debug_file)
248 continue
249
250 # Filter out files based on common issues with the elf file.
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400251 if not os.path.exists(elf_file):
252 # Sometimes we filter out programs from /usr/bin but leave behind
253 # the .debug file.
Ralph Nathan446aee92015-03-23 14:44:56 -0700254 logging.warning('Skipping missing %s', elf_file)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400255 continue
256
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700257 targets.append((os.path.getsize(debug_file), elf_file, debug_file))
258
259 bg_errors = multiprocessing.Value('i')
260 if file_filter:
261 files_not_found = [x for x, found in file_filter.iteritems() if not found]
262 bg_errors.value += len(files_not_found)
263 if files_not_found:
Ralph Nathan59900422015-03-24 10:41:17 -0700264 logging.error('Failed to find requested files: %s', files_not_found)
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700265
266 # Now start generating symbols for the discovered elfs.
267 with parallel.BackgroundTaskRunner(GenerateBreakpadSymbol,
Don Garrette548cff2015-09-23 14:36:21 -0700268 breakpad_dir=breakpad_dir,
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700269 strip_cfi=strip_cfi,
270 num_errors=bg_errors,
271 processes=num_processes) as queue:
272 for _, elf_file, debug_file in sorted(targets, reverse=True):
273 if generate_count == 0:
274 break
275
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400276 queue.put([elf_file, debug_file])
277 if generate_count is not None:
278 generate_count -= 1
279 if generate_count == 0:
280 break
281
282 return bg_errors.value
283
284
Mike Frysinger3f571af2016-08-31 23:56:53 -0400285def FindDebugDir(board, sysroot=None):
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400286 """Given a |board|, return the path to the split debug dir for it"""
Mike Frysinger3f571af2016-08-31 23:56:53 -0400287 if sysroot is None:
288 sysroot = cros_build_lib.GetSysroot(board=board)
Yu-Ju Hongdd9bb2b2014-01-03 17:08:26 -0800289 return os.path.join(sysroot, 'usr', 'lib', 'debug')
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400290
291
Mike Frysinger3f571af2016-08-31 23:56:53 -0400292def FindBreakpadDir(board, sysroot=None):
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400293 """Given a |board|, return the path to the breakpad dir for it"""
Mike Frysinger3f571af2016-08-31 23:56:53 -0400294 return os.path.join(FindDebugDir(board, sysroot=sysroot), 'breakpad')
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400295
296
297def main(argv):
298 parser = commandline.ArgumentParser(description=__doc__)
299
300 parser.add_argument('--board', default=None,
301 help='board to generate symbols for')
302 parser.add_argument('--breakpad_root', type='path', default=None,
Mike Frysinger3f571af2016-08-31 23:56:53 -0400303 help='root output directory for breakpad symbols')
304 parser.add_argument('--sysroot', type='path', default=None,
305 help='root input directory for files')
Shawn Nematbakhsh2c169cb2013-10-29 16:23:58 -0700306 parser.add_argument('--exclude-dir', type=str, action='append',
307 default=[],
308 help='directory (relative to |board| root) to not search')
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400309 parser.add_argument('--generate-count', type=int, default=None,
310 help='only generate # number of symbols')
Mike Frysinger9a628bb2013-10-24 15:51:37 -0400311 parser.add_argument('--noclean', dest='clean', action='store_false',
312 default=True,
313 help='do not clean out breakpad dir before running')
Mike Frysingeref9ab2f2013-08-26 22:16:00 -0400314 parser.add_argument('--jobs', type=int, default=None,
315 help='limit number of parallel jobs')
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400316 parser.add_argument('--strip_cfi', action='store_true', default=False,
317 help='do not generate CFI data (pass -c to dump_syms)')
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700318 parser.add_argument('file_list', nargs='*', default=None,
319 help='generate symbols for only these files '
320 '(e.g. /build/$BOARD/usr/bin/foo)')
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400321
322 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500323 opts.Freeze()
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400324
Mike Frysinger3f571af2016-08-31 23:56:53 -0400325 if opts.board is None and opts.sysroot is None:
326 cros_build_lib.Die('--board or --sysroot is required')
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400327
328 ret = GenerateBreakpadSymbols(opts.board, breakpad_dir=opts.breakpad_root,
329 strip_cfi=opts.strip_cfi,
Mike Frysingeref9ab2f2013-08-26 22:16:00 -0400330 generate_count=opts.generate_count,
Mike Frysinger3f571af2016-08-31 23:56:53 -0400331 sysroot=opts.sysroot,
Mike Frysinger9a628bb2013-10-24 15:51:37 -0400332 num_processes=opts.jobs,
Shawn Nematbakhsh2c169cb2013-10-29 16:23:58 -0700333 clean_breakpad=opts.clean,
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700334 exclude_dirs=opts.exclude_dir,
335 file_list=opts.file_list)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400336 if ret:
Ralph Nathan59900422015-03-24 10:41:17 -0700337 logging.error('encountered %i problem(s)', ret)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400338 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
339 # return 0 in case we are a multiple of the mask.
340 ret = 1
341
342 return ret