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