blob: bca48e6f0d17f9d791d60cc53ca69a9b9b52c581 [file] [log] [blame]
Shuhei Takahashi721d5a72017-02-10 15:38:28 +09001# Copyright 2017 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
7This script takes expanded crash symbols published by the Android build, and
8converts them to breakpad format.
9"""
10
11from __future__ import print_function
12
13import multiprocessing
14import os
15import re
16import zipfile
17
18from chromite.lib import commandline
19from chromite.lib import cros_build_lib
20from chromite.lib import cros_logging as logging
21from chromite.lib import osutils
22from chromite.lib import parallel
23from chromite.scripts import cros_generate_breakpad_symbols
24
25
26RELOCATION_PACKER_BIN = 'relocation_packer'
27
28# These regexps match each type of address we have to adjust.
29ADDRESS_REGEXPS = (
30 re.compile(r'^FUNC ([0-9a-f]+)'),
31 re.compile(r'^([0-9a-f]+)'),
32 re.compile(r'^PUBLIC ([0-9a-f]+)'),
33 re.compile(r'^STACK CFI INIT ([0-9a-f]+)'),
34 re.compile(r'^STACK CFI ([0-9a-f]+)'),
35)
36
37
38class OffsetDiscoveryError(Exception):
39 """Raised if we can't find the offset after unpacking symbols."""
40
41
42def FindExpansionOffset(unpack_result):
43 """Helper to extract symbols offset from relocation_packer output.
44
45 This can accept and handle both successful and failed unpack command output.
46
47 Will return 0 if no adjustment is needed.
48
49 Args:
50 unpack_result: CommandResult from the relocation_packer command.
51
52 Returns:
53 Integer offset to adjust symbols by. May be 0.
54
55 Raises:
56 OffsetDiscoveryError if the unpack succeeds, but we can't parse the output.
57 """
58 if unpack_result.returncode != 0:
59 return 0
60
61 offset_match = re.search(r'INFO: Expansion +: +(\d+) bytes',
62 unpack_result.output)
63
64 if not offset_match:
65 raise OffsetDiscoveryError('No Expansion in: %s' % unpack_result.output)
66
67 # Return offset as a negative number.
68 return -int(offset_match.group(1))
69
70
71def _AdjustLineSymbolOffset(line, offset):
72 """Adjust the symbol offset for one line of a breakpad file.
73
74 Args:
75 line: One line of the file.
76 offset: int to adjust the symbol by.
77
78 Returns:
79 The adjusted line, or original line if there is no change.
80 """
81 for regexp in ADDRESS_REGEXPS:
82 m = regexp.search(line)
83 if m:
84 address = int(m.group(1), 16)
85
86 # We ignore 0 addresses, since the zero's are fillers for unknowns.
87 if address:
88 address += offset
89
90 # Return the same line with address adjusted.
91 return '%s%x%s' % (line[:m.start(1)], address, line[m.end(1):])
92
93 # Nothing recognized, no adjustment.
94 return line
95
96
97def _AdjustSymbolOffset(breakpad_file, offset):
98 """Given a breakpad file, adjust the symbols by offset.
99
100 Updates the file in place.
101
102 Args:
103 breakpad_file: File to read and update in place.
104 offset: Integer to move symbols by.
105 """
106 logging.info('Adjusting symbols in %s with offset %d.',
107 breakpad_file, offset)
108
109 # Keep newlines.
110 lines = osutils.ReadFile(breakpad_file).splitlines(True)
111 adjusted_lines = [_AdjustLineSymbolOffset(line, offset) for line in lines]
112 osutils.WriteFile(breakpad_file, ''.join(adjusted_lines))
113
114
115def _UnpackGenerateBreakpad(elf_file, *args, **kwargs):
116 """Unpack Android relocation symbols, and GenerateBreakpadSymbol
117
118 This method accepts exactly the same arguments as
119 cros_generate_breakpad_symbols.GenerateBreakpadSymbol, except that it requires
120 elf_file, and fills in dump_sym_cmd.
121
122 Args:
123 elf_file: Name of the file to generate breakpad symbols for.
124 args: See cros_generate_breakpad_symbols.GenerateBreakpadSymbol.
125 kwargs: See cros_generate_breakpad_symbols.GenerateBreakpadSymbol.
126 """
127 # We try to unpack, and just see if it works. Real failures caused by
128 # something other than a binary that's already unpacked will be logged and
129 # ignored. We'll notice them when dump_syms fails later (which it will on
130 # packed binaries.).
131 unpack_cmd = [RELOCATION_PACKER_BIN, '-u', elf_file]
132 unpack_result = cros_build_lib.RunCommand(
133 unpack_cmd, redirect_stdout=True, error_code_ok=True)
134
135 # If we unpacked, extract the offset, and remember it.
136 offset = FindExpansionOffset(unpack_result)
137
138 if offset:
139 logging.info('Unpacked relocation symbols for %s with offset %d.',
140 elf_file, offset)
141
142 # Now generate breakpad symbols from the binary.
143 breakpad_file = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
144 elf_file, *args, **kwargs)
145
146 if offset:
147 _AdjustSymbolOffset(breakpad_file, offset)
148
149
150def GenerateBreakpadSymbols(breakpad_dir, symbols_dir):
151 """Generate symbols for all binaries in symbols_dir.
152
153 Args:
154 breakpad_dir: The full path in which to write out breakpad symbols.
155 symbols_dir: The full path to the binaries to process from.
156
157 Returns:
158 The number of errors that were encountered.
159 """
160 osutils.SafeMakedirs(breakpad_dir)
161 logging.info('generating breakpad symbols from %s', symbols_dir)
162
163 num_errors = multiprocessing.Value('i')
164
165 # Now start generating symbols for the discovered elfs.
166 with parallel.BackgroundTaskRunner(
167 _UnpackGenerateBreakpad,
168 breakpad_dir=breakpad_dir,
169 num_errors=num_errors) as queue:
170
171 for root, _, files in os.walk(symbols_dir):
172 for f in files:
173 queue.put([os.path.join(root, f)])
174
175 return num_errors.value
176
177
178def ProcessSymbolsZip(zip_archive, breakpad_dir):
179 """Extract, process, and upload all symbols in a symbols zip file.
180
181 Take the symbols file build artifact from an Android build, process it into
182 breakpad format, and upload the results to the ChromeOS crashreporter.
183 Significant multiprocessing is done by helper libraries, and a remote swarm
184 server is used to reduce processing of duplicate symbol files.
185
186 The symbols files are really expected to be unstripped elf files (or
187 libraries), possibly using packed relocation tables. No other file types are
188 expected in the zip.
189
190 Args:
191 zip_archive: Name of the zip file to process.
192 breakpad_dir: Root directory for writing out breakpad files.
193 """
194 with osutils.TempDir(prefix='extracted-') as extract_dir:
195 logging.info('Extracting %s into %s', zip_archive, extract_dir)
196 with zipfile.ZipFile(zip_archive, 'r') as zf:
197 # We are trusting the contents from a security point of view.
198 zf.extractall(extract_dir)
199
200 logging.info('Generate breakpad symbols from %s into %s',
201 extract_dir, breakpad_dir)
202 GenerateBreakpadSymbols(breakpad_dir, extract_dir)
203
204
205def main(argv):
206 """Helper method mostly used for manual testing."""
207
208 parser = commandline.ArgumentParser(description=__doc__)
209
210 parser.add_argument('--symbols_file', type='path', required=True,
211 help='Zip file containing')
212 parser.add_argument('--breakpad_dir', type='path', default='/tmp/breakpad',
213 help='Root directory for breakpad symbol files.')
214
215 opts = parser.parse_args(argv)
216 opts.Freeze()
217
218 ProcessSymbolsZip(opts.symbols_file, opts.breakpad_dir)