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."""
18 def VerifySymbolAndCopyToTempDir(symbol_file
, temp_dir
, sym_module_name
):
19 """Verify the symbol file looks correct and copy it to the right place
23 symbol_file: the path to the symbol file.
24 temp_dir: the base of the temp directory where the symbol file will reside.
28 symbol
= open(symbol_file
)
29 signature_line
= symbol
.readline().strip().split()
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])
38 dest_file
= os
.path
.join(dest
, '%s.sym' % signature_line
[4])
39 shutil
.copyfile(symbol_file
, dest_file
)
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.
54 proc
= subprocess
.Popen(command
, stdout
=subprocess
.PIPE
,
55 stderr
=subprocess
.STDOUT
, bufsize
=1)
56 output
= proc
.communicate()[0]
58 raise OSError('%s: %s' % (subprocess
.list2cmdline(command
), output
))
62 def GetCrashDumpDir():
63 """Returns the default crash dump directory used by Chromium."""
64 config_home
= os
.environ
.get('XDG_CONFIG_HOME')
66 home
= os
.path
.expanduser('~')
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.
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.
81 A string representing the stack trace.
83 # Run processor to analyze crash dump.
84 cmd
= [processor_bin
, '-m', dump_file
, symbol_path
]
87 output
= GetCommandOutput(cmd
)
89 return 'Cannot get stack trace.'
91 # Retrieve stack trace from processor output. Processor output looks like:
101 # ... |--- crashed thread stack trace
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')
112 output
= output
[idx
+1:]
113 idx
= output
.find('\n\n')
115 output
= output
[idx
+2:].splitlines()
117 first_line
= output
[0].split('|')
119 crashed_thread
= first_line
[0]
121 line_split
= line
.split('|')
124 if line_split
[0] != crashed_thread
:
126 stack_trace_frames
.append(line_split
)
127 if not stack_trace_frames
:
128 return 'Cannot get stack trace.'
130 for frame
in stack_trace_frames
:
133 (exe
, func
, source
, line
, offset
) = frame
[2:]
134 if not exe
or not source
or not line
or not offset
:
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.
162 dump_file: the dump file that needs to be processed.
163 temp_dir: the temp directory to put the dump file in.
165 path of the processed dump file.
167 dump
= open(dump_file
, 'rb')
168 dump_data
= dump
.read()
170 idx
= dump_data
.find('MDMP')
174 dump_data
= dump_data
[idx
:]
177 (dump_fd
, dump_name
) = tempfile
.mkstemp(suffix
='chromedump', dir=temp_dir
)
178 os
.write(dump_fd
, dump_data
)
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'
188 if options
.processor_dir
:
189 bin
= os
.path
.join(os
.path
.expanduser(options
.processor_dir
),
191 if os
.access(bin
, os
.X_OK
):
194 for path
in os
.environ
['PATH'].split(':'):
195 bin
= os
.path
.join(path
, LINUX_PROCESSOR
)
196 if os
.access(bin
, os
.X_OK
):
199 if not processor_bin
:
200 print 'Cannot find minidump_stackwalk.'
203 if options
.symbol_filename
:
204 symbol_file
= options
.symbol_filename
206 if options
.architecture
:
207 bits
= options
.architecture
209 bits
= struct
.calcsize('P') * 8
211 symbol_file
= 'chrome.breakpad.ia32'
213 symbol_file
= 'chrome.breakpad.x64'
215 print 'Unknown architecture'
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.'
226 symbol_time
= os
.path
.getmtime(symbol_file
)
229 if options
.dump_file
:
230 dump_files
.append(options
.dump_file
)
232 dump_dir
= options
.dump_dir
234 dump_dir
= GetCrashDumpDir()
236 print 'Cannot find dump files.'
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.
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
)
253 for dump_file
in dump_files
:
254 processed_dump_file
= ProcessDump(dump_file
, temp_dir
)
255 if not processed_dump_file
:
257 print '-------------------------'
258 print GetStackTrace(processor_bin
, temp_dir
, processed_dump_file
)
260 os
.remove(processed_dump_file
)
263 shutil
.rmtree(temp_dir
)
264 print '%s dumps found' % dump_count
269 if not sys
.platform
.startswith('linux'):
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. '
295 (options
, args
) = parser
.parse_args()
296 return main_linux(options
, args
)
299 if '__main__' == __name__
: