Respond with QuotaExceededError when IndexedDB has no disk space on open.
[chromium-blink-merge.git] / chrome / tools / process_dumps / process_dumps_linux.py
blob1f0ba9d898ecff02b315158d707499b8335805d0
1 #!/usr/bin/env python
2 # Copyright (c) 2011 The Chromium 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.
6 """A tool to collect crash signatures for Chrome builds on Linux."""
8 import fnmatch
9 import optparse
10 import os
11 import shutil
12 import subprocess
13 import struct
14 import sys
15 import tempfile
18 def VerifySymbolAndCopyToTempDir(symbol_file, temp_dir, sym_module_name):
19 """Verify the symbol file looks correct and copy it to the right place
20 in temp_dir.
22 Args:
23 symbol_file: the path to the symbol file.
24 temp_dir: the base of the temp directory where the symbol file will reside.
25 Returns:
26 True on success.
27 """
28 symbol = open(symbol_file)
29 signature_line = symbol.readline().strip().split()
30 symbol.close()
31 # signature_line should look like:
32 # MODULE Linux x86 28D8A79A426807B5462CBA24F56746750 chrome
33 if (len(signature_line) == 5 and signature_line[0] == 'MODULE' and
34 signature_line[1] == 'Linux' and signature_line[4] == sym_module_name and
35 len(signature_line[3]) == 33):
36 dest = os.path.join(temp_dir, signature_line[4], signature_line[3])
37 os.makedirs(dest)
38 dest_file = os.path.join(dest, '%s.sym' % signature_line[4])
39 shutil.copyfile(symbol_file, dest_file)
40 return True
41 return False
44 def GetCommandOutput(command):
45 """Runs the command list, returning its output.
47 Prints the given command (which should be a list of one or more strings),
48 then runs it and returns its output (stdout and stderr) as a string.
50 If the command exits with an error, raises OSError.
52 From chromium_utils.
53 """
54 proc = subprocess.Popen(command, stdout=subprocess.PIPE,
55 stderr=subprocess.STDOUT, bufsize=1)
56 output = proc.communicate()[0]
57 if proc.returncode:
58 raise OSError('%s: %s' % (subprocess.list2cmdline(command), output))
59 return output
62 def GetCrashDumpDir():
63 """Returns the default crash dump directory used by Chromium."""
64 config_home = os.environ.get('XDG_CONFIG_HOME')
65 if not config_home:
66 home = os.path.expanduser('~')
67 if not home:
68 return ''
69 config_home = os.path.join(home, '.config')
70 return os.path.join(config_home, 'chromium', 'Crash Reports')
73 def GetStackTrace(processor_bin, symbol_path, dump_file):
74 """Gets and prints the stack trace from a crash dump file.
76 Args:
77 processor_bin: the path to the processor.
78 symbol_path: root dir for the symbols.
79 dump_file: the path to the dump file.
80 Returns:
81 A string representing the stack trace.
82 """
83 # Run processor to analyze crash dump.
84 cmd = [processor_bin, '-m', dump_file, symbol_path]
86 try:
87 output = GetCommandOutput(cmd)
88 except OSError:
89 return 'Cannot get stack trace.'
91 # Retrieve stack trace from processor output. Processor output looks like:
92 # ----------------
93 # Debug output
94 # ...
95 # Debug output
96 # Module ...
97 # ...
98 # Module ...
100 # N|... <--+
101 # ... |--- crashed thread stack trace
102 # N|... <--+
103 # M|...
104 # ...
105 # ----------------
106 # where each line of the stack trace looks like:
107 # ThreadNumber|FrameNumber|ExeName|Function|SourceFile|LineNo|Offset
109 stack_trace_frames = []
110 idx = output.find('\nModule')
111 if idx >= 0:
112 output = output[idx+1:]
113 idx = output.find('\n\n')
114 if idx >= 0:
115 output = output[idx+2:].splitlines()
116 if output:
117 first_line = output[0].split('|')
118 if first_line:
119 crashed_thread = first_line[0]
120 for line in output:
121 line_split = line.split('|')
122 if not line_split:
123 break
124 if line_split[0] != crashed_thread:
125 break
126 stack_trace_frames.append(line_split)
127 if not stack_trace_frames:
128 return 'Cannot get stack trace.'
129 stack_trace = []
130 for frame in stack_trace_frames:
131 if len(frame) != 7:
132 continue
133 (exe, func, source, line, offset) = frame[2:]
134 if not exe or not source or not line or not offset:
135 continue
136 idx = func.find('(')
137 if idx >= 0:
138 func = func[:idx]
139 if not func:
140 continue
141 frame_output = '%s!%s+%s [%s @ %s]' % (exe, func, offset, source, line)
142 stack_trace.append(frame_output)
143 return '\n'.join(stack_trace)
146 def LocateFiles(pattern, root=os.curdir):
147 """Yields files matching pattern found in root and its subdirectories.
149 An exception is thrown if root doesn't exist.
151 From chromium_utils."""
152 root = os.path.expanduser(root)
153 for path, dirs, files in os.walk(os.path.abspath(root)):
154 for filename in fnmatch.filter(files, pattern):
155 yield os.path.join(path, filename)
158 def ProcessDump(dump_file, temp_dir):
159 """Extracts the part of the dump file that minidump_stackwalk can read.
161 Args:
162 dump_file: the dump file that needs to be processed.
163 temp_dir: the temp directory to put the dump file in.
164 Returns:
165 path of the processed dump file.
167 dump = open(dump_file, 'rb')
168 dump_data = dump.read()
169 dump.close()
170 idx = dump_data.find('MDMP')
171 if idx < 0:
172 return ''
174 dump_data = dump_data[idx:]
175 if not dump_data:
176 return ''
177 (dump_fd, dump_name) = tempfile.mkstemp(suffix='chromedump', dir=temp_dir)
178 os.write(dump_fd, dump_data)
179 os.close(dump_fd)
180 return dump_name
183 def main_linux(options, args):
184 # minidump_stackwalk is part of Google Breakpad. You may need to checkout
185 # the code and build your own copy. http://google-breakpad.googlecode.com/
186 LINUX_PROCESSOR = 'minidump_stackwalk'
187 processor_bin = None
188 if options.processor_dir:
189 bin = os.path.join(os.path.expanduser(options.processor_dir),
190 LINUX_PROCESSOR)
191 if os.access(bin, os.X_OK):
192 processor_bin = bin
193 else:
194 for path in os.environ['PATH'].split(':'):
195 bin = os.path.join(path, LINUX_PROCESSOR)
196 if os.access(bin, os.X_OK):
197 processor_bin = bin
198 break
199 if not processor_bin:
200 print 'Cannot find minidump_stackwalk.'
201 return 1
203 if options.symbol_filename:
204 symbol_file = options.symbol_filename
205 else:
206 if options.architecture:
207 bits = options.architecture
208 else:
209 bits = struct.calcsize('P') * 8
210 if bits == 32:
211 symbol_file = 'chrome.breakpad.ia32'
212 elif bits == 64:
213 symbol_file = 'chrome.breakpad.x64'
214 else:
215 print 'Unknown architecture'
216 return 1
218 symbol_dir = options.symbol_dir
219 if not options.symbol_dir:
220 symbol_dir = os.curdir
221 symbol_dir = os.path.abspath(os.path.expanduser(symbol_dir))
222 symbol_file = os.path.join(symbol_dir, symbol_file)
223 if not os.path.exists(symbol_file):
224 print 'Cannot find symbols.'
225 return 1
226 symbol_time = os.path.getmtime(symbol_file)
228 dump_files = []
229 if options.dump_file:
230 dump_files.append(options.dump_file)
231 else:
232 dump_dir = options.dump_dir
233 if not dump_dir:
234 dump_dir = GetCrashDumpDir()
235 if not dump_dir:
236 print 'Cannot find dump files.'
237 return 1
238 for dump_file in LocateFiles(pattern='*.dmp', root=dump_dir):
239 file_time = os.path.getmtime(dump_file)
240 if file_time < symbol_time:
241 # Ignore dumps older than symbol file.
242 continue
243 dump_files.append(dump_file)
245 temp_dir = tempfile.mkdtemp(suffix='chromedump')
246 if not VerifySymbolAndCopyToTempDir(symbol_file, temp_dir,
247 options.sym_module_name):
248 print 'Cannot parse symbols.'
249 shutil.rmtree(temp_dir)
250 return 1
252 dump_count = 0
253 for dump_file in dump_files:
254 processed_dump_file = ProcessDump(dump_file, temp_dir)
255 if not processed_dump_file:
256 continue
257 print '-------------------------'
258 print GetStackTrace(processor_bin, temp_dir, processed_dump_file)
259 print
260 os.remove(processed_dump_file)
261 dump_count += 1
263 shutil.rmtree(temp_dir)
264 print '%s dumps found' % dump_count
265 return 0
268 def main():
269 if not sys.platform.startswith('linux'):
270 return 1
271 parser = optparse.OptionParser()
272 parser.add_option('', '--processor-dir', type='string', default='',
273 help='The directory where the processor is installed. '
274 'The processor is used to get stack trace from dumps. '
275 'Searches $PATH by default')
276 parser.add_option('', '--dump-file', type='string', default='',
277 help='The path of the dump file to be processed. '
278 'Overwrites dump-path.')
279 parser.add_option('', '--dump-dir', type='string', default='',
280 help='The directory where dump files are stored. '
281 'Searches this directory if dump-file is not '
282 'specified. Default is the Chromium crash directory.')
283 parser.add_option('', '--symbol-dir', default='',
284 help='The directory with the symbols file. [Required]')
285 parser.add_option('', '--symbol-filename', default='',
286 help='The name of the symbols file to use. '
287 'This argument overrides --architecture.')
288 parser.add_option('', '--architecture', type='int', default=None,
289 help='Override automatic x86/x86-64 detection. '
290 'Valid values are 32 and 64')
291 parser.add_option('', '--sym-module-name', type='string', default='chrome',
292 help='The module name for the symbol file. '
293 'Default: chrome')
295 (options, args) = parser.parse_args()
296 return main_linux(options, args)
299 if '__main__' == __name__:
300 sys.exit(main())