2 # Copyright (c) 2009 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
):
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] == 'chrome' 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
.architecture
:
204 bits
= options
.architecture
206 bits
= struct
.calcsize('P') * 8
208 symbol_file
= 'chrome.breakpad.ia32'
210 symbol_file
= 'chrome.breakpad.x64'
212 print 'Unknown architecture'
215 symbol_dir
= options
.symbol_dir
216 if not options
.symbol_dir
:
217 symbol_dir
= os
.curdir
218 symbol_dir
= os
.path
.abspath(os
.path
.expanduser(symbol_dir
))
219 symbol_file
= os
.path
.join(symbol_dir
, symbol_file
)
220 if not os
.path
.exists(symbol_file
):
221 print 'Cannot find symbols.'
223 symbol_time
= os
.path
.getmtime(symbol_file
)
226 if options
.dump_file
:
227 dump_files
.append(options
.dump_file
)
229 dump_dir
= options
.dump_dir
231 dump_dir
= GetCrashDumpDir()
233 print 'Cannot find dump files.'
235 for dump_file
in LocateFiles(pattern
='*.dmp', root
=dump_dir
):
236 file_time
= os
.path
.getmtime(dump_file
)
237 if file_time
< symbol_time
:
238 # Ignore dumps older than symbol file.
240 dump_files
.append(dump_file
)
242 temp_dir
= tempfile
.mkdtemp(suffix
='chromedump')
243 if not VerifySymbolAndCopyToTempDir(symbol_file
, temp_dir
):
244 print 'Cannot parse symbols.'
245 shutil
.rmtree(temp_dir
)
249 for dump_file
in dump_files
:
250 processed_dump_file
= ProcessDump(dump_file
, temp_dir
)
251 if not processed_dump_file
:
253 print '-------------------------'
254 print GetStackTrace(processor_bin
, temp_dir
, processed_dump_file
)
256 os
.remove(processed_dump_file
)
259 shutil
.rmtree(temp_dir
)
260 print '%s dumps found' % dump_count
264 if '__main__' == __name__
:
265 parser
= optparse
.OptionParser()
266 parser
.add_option('', '--processor-dir', type='string', default
='',
267 help='The directory where the processor is installed. '
268 'The processor is used to get stack trace from dumps. '
269 'Searches $PATH by default')
270 parser
.add_option('', '--dump-file', type='string', default
='',
271 help='The path of the dump file to be processed. '
272 'Overwrites dump-path.')
273 parser
.add_option('', '--dump-dir', type='string', default
='',
274 help='The directory where dump files are stored. '
275 'Searches this directory if dump-file is not '
276 'specified. Default is the Chromium crash directory.')
277 parser
.add_option('', '--symbol-dir', default
='',
278 help='The directory with the symbols file. [Required]')
279 parser
.add_option('', '--architecture', type='int', default
=None,
280 help='Override automatic x86/x86-64 detection. '
281 'Valid values are 32 and 64')
283 (options
, args
) = parser
.parse_args()
285 if sys
.platform
== 'linux2':
286 sys
.exit(main_linux(options
, args
))