blob: 73990033ed9c5be0a45ae0675bb0fe2a0c43f26e [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
62 offset_match = re.search(r'INFO: Expansion +: +(\d+) bytes',
63 unpack_result.output)
64
65 if not offset_match:
66 raise OffsetDiscoveryError('No Expansion in: %s' % unpack_result.output)
67
68 # Return offset as a negative number.
69 return -int(offset_match.group(1))
70
71
72def _AdjustLineSymbolOffset(line, offset):
73 """Adjust the symbol offset for one line of a breakpad file.
74
75 Args:
76 line: One line of the file.
77 offset: int to adjust the symbol by.
78
79 Returns:
80 The adjusted line, or original line if there is no change.
81 """
82 for regexp in ADDRESS_REGEXPS:
83 m = regexp.search(line)
84 if m:
85 address = int(m.group(1), 16)
86
87 # We ignore 0 addresses, since the zero's are fillers for unknowns.
88 if address:
89 address += offset
90
91 # Return the same line with address adjusted.
92 return '%s%x%s' % (line[:m.start(1)], address, line[m.end(1):])
93
94 # Nothing recognized, no adjustment.
95 return line
96
97
98def _AdjustSymbolOffset(breakpad_file, offset):
99 """Given a breakpad file, adjust the symbols by offset.
100
101 Updates the file in place.
102
103 Args:
104 breakpad_file: File to read and update in place.
105 offset: Integer to move symbols by.
106 """
107 logging.info('Adjusting symbols in %s with offset %d.',
108 breakpad_file, offset)
109
110 # Keep newlines.
111 lines = osutils.ReadFile(breakpad_file).splitlines(True)
112 adjusted_lines = [_AdjustLineSymbolOffset(line, offset) for line in lines]
113 osutils.WriteFile(breakpad_file, ''.join(adjusted_lines))
114
115
116def _UnpackGenerateBreakpad(elf_file, *args, **kwargs):
117 """Unpack Android relocation symbols, and GenerateBreakpadSymbol
118
119 This method accepts exactly the same arguments as
120 cros_generate_breakpad_symbols.GenerateBreakpadSymbol, except that it requires
121 elf_file, and fills in dump_sym_cmd.
122
123 Args:
124 elf_file: Name of the file to generate breakpad symbols for.
125 args: See cros_generate_breakpad_symbols.GenerateBreakpadSymbol.
126 kwargs: See cros_generate_breakpad_symbols.GenerateBreakpadSymbol.
127 """
128 # We try to unpack, and just see if it works. Real failures caused by
129 # something other than a binary that's already unpacked will be logged and
130 # ignored. We'll notice them when dump_syms fails later (which it will on
131 # packed binaries.).
132 unpack_cmd = [RELOCATION_PACKER_BIN, '-u', elf_file]
133 unpack_result = cros_build_lib.RunCommand(
134 unpack_cmd, redirect_stdout=True, error_code_ok=True)
135
136 # If we unpacked, extract the offset, and remember it.
137 offset = FindExpansionOffset(unpack_result)
138
139 if offset:
140 logging.info('Unpacked relocation symbols for %s with offset %d.',
141 elf_file, offset)
142
143 # Now generate breakpad symbols from the binary.
144 breakpad_file = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
145 elf_file, *args, **kwargs)
146
147 if offset:
148 _AdjustSymbolOffset(breakpad_file, offset)
149
150
151def GenerateBreakpadSymbols(breakpad_dir, symbols_dir):
152 """Generate symbols for all binaries in symbols_dir.
153
154 Args:
155 breakpad_dir: The full path in which to write out breakpad symbols.
156 symbols_dir: The full path to the binaries to process from.
157
158 Returns:
159 The number of errors that were encountered.
160 """
161 osutils.SafeMakedirs(breakpad_dir)
162 logging.info('generating breakpad symbols from %s', symbols_dir)
163
164 num_errors = multiprocessing.Value('i')
165
166 # Now start generating symbols for the discovered elfs.
167 with parallel.BackgroundTaskRunner(
168 _UnpackGenerateBreakpad,
169 breakpad_dir=breakpad_dir,
170 num_errors=num_errors) as queue:
171
172 for root, _, files in os.walk(symbols_dir):
173 for f in files:
174 queue.put([os.path.join(root, f)])
175
176 return num_errors.value
177
178
179def ProcessSymbolsZip(zip_archive, breakpad_dir):
180 """Extract, process, and upload all symbols in a symbols zip file.
181
182 Take the symbols file build artifact from an Android build, process it into
183 breakpad format, and upload the results to the ChromeOS crashreporter.
184 Significant multiprocessing is done by helper libraries, and a remote swarm
185 server is used to reduce processing of duplicate symbol files.
186
187 The symbols files are really expected to be unstripped elf files (or
188 libraries), possibly using packed relocation tables. No other file types are
189 expected in the zip.
190
191 Args:
192 zip_archive: Name of the zip file to process.
193 breakpad_dir: Root directory for writing out breakpad files.
194 """
195 with osutils.TempDir(prefix='extracted-') as extract_dir:
196 logging.info('Extracting %s into %s', zip_archive, extract_dir)
197 with zipfile.ZipFile(zip_archive, 'r') as zf:
198 # We are trusting the contents from a security point of view.
199 zf.extractall(extract_dir)
200
201 logging.info('Generate breakpad symbols from %s into %s',
202 extract_dir, breakpad_dir)
203 GenerateBreakpadSymbols(breakpad_dir, extract_dir)
204
205
206def main(argv):
207 """Helper method mostly used for manual testing."""
208
209 parser = commandline.ArgumentParser(description=__doc__)
210
211 parser.add_argument('--symbols_file', type='path', required=True,
212 help='Zip file containing')
213 parser.add_argument('--breakpad_dir', type='path', default='/tmp/breakpad',
214 help='Root directory for breakpad symbol files.')
215
216 opts = parser.parse_args(argv)
217 opts.Freeze()
218
219 ProcessSymbolsZip(opts.symbols_file, opts.breakpad_dir)