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