roll skia 1111->1115
[chromium-blink-merge.git] / chrome / tools / process_dumps_linux.py
blob728c640b20913bbd89910d047165dda748e283cd
1 #!/usr/bin/python
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."""
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):
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] == 'chrome' 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.architecture:
204 bits = options.architecture
205 else:
206 bits = struct.calcsize('P') * 8
207 if bits == 32:
208 symbol_file = 'chrome.breakpad.ia32'
209 elif bits == 64:
210 symbol_file = 'chrome.breakpad.x64'
211 else:
212 print 'Unknown architecture'
213 return 1
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.'
222 return 1
223 symbol_time = os.path.getmtime(symbol_file)
225 dump_files = []
226 if options.dump_file:
227 dump_files.append(options.dump_file)
228 else:
229 dump_dir = options.dump_dir
230 if not dump_dir:
231 dump_dir = GetCrashDumpDir()
232 if not dump_dir:
233 print 'Cannot find dump files.'
234 return 1
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.
239 continue
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)
246 return 1
248 dump_count = 0
249 for dump_file in dump_files:
250 processed_dump_file = ProcessDump(dump_file, temp_dir)
251 if not processed_dump_file:
252 continue
253 print '-------------------------'
254 print GetStackTrace(processor_bin, temp_dir, processed_dump_file)
255 print
256 os.remove(processed_dump_file)
257 dump_count += 1
259 shutil.rmtree(temp_dir)
260 print '%s dumps found' % dump_count
261 return 0
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))
287 else:
288 sys.exit(1)