| OLD | NEW |
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 # memcheck_analyze.py | 6 # memcheck_analyze.py |
| 7 | 7 |
| 8 ''' Given a valgrind XML file, parses errors and uniques them.''' | 8 ''' Given a valgrind XML file, parses errors and uniques them.''' |
| 9 | 9 |
| 10 import gdb_helper | 10 import gdb_helper |
| 11 | 11 |
| 12 import hashlib | 12 import hashlib |
| 13 import logging | 13 import logging |
| 14 import optparse | 14 import optparse |
| 15 import os | 15 import os |
| 16 import re | 16 import re |
| 17 import subprocess | 17 import subprocess |
| 18 import sys | 18 import sys |
| 19 import time | 19 import time |
| 20 from xml.dom.minidom import parse | 20 from xml.dom.minidom import parse |
| 21 from xml.parsers.expat import ExpatError | 21 from xml.parsers.expat import ExpatError |
| 22 | 22 |
| 23 import common | 23 import common |
| 24 | 24 |
| 25 # Global symbol table (yuck) | 25 # Global symbol table (yuck) |
| 26 TheAddressTable = None | 26 TheAddressTable = None |
| 27 | 27 |
| 28 # These are functions (using C++ mangled names) that we look for in stack | 28 # These are regexps that define functions (using C++ mangled names) |
| 29 # traces. We don't show stack frames while pretty printing when they are below | 29 # we don't want to see in stack traces while pretty printing |
| 30 # any of the following: | 30 # or generating suppressions. |
| 31 _TOP_OF_STACK_POINTS = [ | 31 # Just stop printing the stack/suppression frames when the current one |
| 32 # Don't show our testing framework. | 32 # matches any of these. |
| 33 "testing::Test::Run()", | 33 _BORING_CALLERS = [ |
| 34 # TODO(timurrrr): add more boring callers when needed |
| 35 |
| 36 # Don't show our testing framework: |
| 34 "_ZN7testing4Test3RunEv", | 37 "_ZN7testing4Test3RunEv", |
| 38 "_ZN7testing8internal35HandleExceptionsInMethodIfSupported.*", |
| 39 "_ZN7testing8internal38HandleSehExceptionsInMethodIfSupported.*", |
| 40 |
| 41 # Depends on scheduling: |
| 42 "_ZN11MessageLoop3RunEv", |
| 43 "_ZN14RunnableMethod.*", |
| 44 |
| 35 # Also don't show the internals of libc/pthread. | 45 # Also don't show the internals of libc/pthread. |
| 36 "start_thread" | 46 "start_thread" |
| 37 ] | 47 ] |
| 38 | 48 |
| 39 def getTextOf(top_node, name): | 49 def getTextOf(top_node, name): |
| 40 ''' Returns all text in all DOM nodes with a certain |name| that are children | 50 ''' Returns all text in all DOM nodes with a certain |name| that are children |
| 41 of |top_node|. | 51 of |top_node|. |
| 42 ''' | 52 ''' |
| 43 | 53 |
| 44 text = "" | 54 text = "" |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 88 for frame in node.getElementsByTagName("frame"): | 98 for frame in node.getElementsByTagName("frame"): |
| 89 frame_dict = { | 99 frame_dict = { |
| 90 INSTRUCTION_POINTER : getTextOf(frame, INSTRUCTION_POINTER), | 100 INSTRUCTION_POINTER : getTextOf(frame, INSTRUCTION_POINTER), |
| 91 OBJECT_FILE : getTextOf(frame, OBJECT_FILE), | 101 OBJECT_FILE : getTextOf(frame, OBJECT_FILE), |
| 92 FUNCTION_NAME : getTextOf(frame, FUNCTION_NAME), | 102 FUNCTION_NAME : getTextOf(frame, FUNCTION_NAME), |
| 93 SRC_FILE_DIR : shortenFilePath( | 103 SRC_FILE_DIR : shortenFilePath( |
| 94 source_dir, getTextOf(frame, SRC_FILE_DIR)), | 104 source_dir, getTextOf(frame, SRC_FILE_DIR)), |
| 95 SRC_FILE_NAME : getTextOf(frame, SRC_FILE_NAME), | 105 SRC_FILE_NAME : getTextOf(frame, SRC_FILE_NAME), |
| 96 SRC_LINE : getTextOf(frame, SRC_LINE) | 106 SRC_LINE : getTextOf(frame, SRC_LINE) |
| 97 } | 107 } |
| 108 |
| 109 # Ignore this frame and all the following if it's a "boring" function. |
| 110 enough_frames = False |
| 111 for regexp in _BORING_CALLERS: |
| 112 if re.match("^%s$" % regexp, frame_dict[FUNCTION_NAME]): |
| 113 enough_frames = True |
| 114 break |
| 115 if enough_frames: |
| 116 break |
| 117 |
| 98 frames += [frame_dict] | 118 frames += [frame_dict] |
| 99 if frame_dict[FUNCTION_NAME] in _TOP_OF_STACK_POINTS: | 119 |
| 100 break | |
| 101 global TheAddressTable | 120 global TheAddressTable |
| 102 if TheAddressTable != None and frame_dict[SRC_LINE] == "": | 121 if TheAddressTable != None and frame_dict[SRC_LINE] == "": |
| 103 # Try using gdb | 122 # Try using gdb |
| 104 TheAddressTable.Add(frame_dict[OBJECT_FILE], | 123 TheAddressTable.Add(frame_dict[OBJECT_FILE], |
| 105 frame_dict[INSTRUCTION_POINTER]) | 124 frame_dict[INSTRUCTION_POINTER]) |
| 106 return frames | 125 return frames |
| 107 | 126 |
| 108 class ValgrindError: | 127 class ValgrindError: |
| 109 ''' Takes a <DOM Element: error> node and reads all the data from it. A | 128 ''' Takes a <DOM Element: error> node and reads all the data from it. A |
| 110 ValgrindError is immutable and is hashed on its pretty printed output. | 129 ValgrindError is immutable and is hashed on its pretty printed output. |
| (...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 258 output += "Suppression (error hash=#%016X#):\n" % self.ErrorHash() | 277 output += "Suppression (error hash=#%016X#):\n" % self.ErrorHash() |
| 259 output += (" For more info on using suppressions see " | 278 output += (" For more info on using suppressions see " |
| 260 "http://dev.chromium.org/developers/how-tos/using-valgrind#TOC-Su
ppressing-Errors") | 279 "http://dev.chromium.org/developers/how-tos/using-valgrind#TOC-Su
ppressing-Errors") |
| 261 | 280 |
| 262 # Widen suppression slightly to make portable between mac and linux | 281 # Widen suppression slightly to make portable between mac and linux |
| 263 supp = self._suppression; | 282 supp = self._suppression; |
| 264 supp = supp.replace("fun:_Znwj", "fun:_Znw*") | 283 supp = supp.replace("fun:_Znwj", "fun:_Znw*") |
| 265 supp = supp.replace("fun:_Znwm", "fun:_Znw*") | 284 supp = supp.replace("fun:_Znwm", "fun:_Znw*") |
| 266 supp = supp.replace("fun:_Znaj", "fun:_Zna*") | 285 supp = supp.replace("fun:_Znaj", "fun:_Zna*") |
| 267 supp = supp.replace("fun:_Znam", "fun:_Zna*") | 286 supp = supp.replace("fun:_Znam", "fun:_Zna*") |
| 287 |
| 268 # Split into lines so we can enforce length limits | 288 # Split into lines so we can enforce length limits |
| 269 supplines = supp.split("\n") | 289 supplines = supp.split("\n") |
| 290 supp = None # to avoid re-use |
| 270 | 291 |
| 271 # Truncate at line 26 (VG_MAX_SUPP_CALLERS plus 2 for name and type) | 292 # Truncate at line 26 (VG_MAX_SUPP_CALLERS plus 2 for name and type) |
| 272 # or at the first 'boring' caller. | 293 # or at the first 'boring' caller. |
| 273 # (https://bugs.kde.org/show_bug.cgi?id=199468 proposes raising | 294 # (https://bugs.kde.org/show_bug.cgi?id=199468 proposes raising |
| 274 # VG_MAX_SUPP_CALLERS, but we're probably fine with it as is.) | 295 # VG_MAX_SUPP_CALLERS, but we're probably fine with it as is.) |
| 275 # TODO(dkegel): add more boring callers | 296 newlen = min(26, len(supplines)); |
| 276 newlen = 26; | 297 |
| 277 for boring_caller in [" fun:_ZN11MessageLoop3RunEv", | 298 # Drop boring frames and all the following. |
| 278 " fun:_ZN7testing4Test3RunEv"]: | 299 enough_frames = False |
| 279 try: | 300 for frameno in range(newlen): |
| 280 newlen = min(newlen, supplines.index(boring_caller)) | 301 for boring_caller in _BORING_CALLERS: |
| 281 except ValueError: | 302 if re.match("^ +fun:%s$" % boring_caller, supplines[frameno]): |
| 282 pass | 303 newlen = frameno |
| 304 enough_frames = True |
| 305 break |
| 306 if enough_frames: |
| 307 break |
| 283 if (len(supplines) > newlen): | 308 if (len(supplines) > newlen): |
| 284 supplines = supplines[0:newlen] | 309 supplines = supplines[0:newlen] |
| 285 supplines.append("}") | 310 supplines.append("}") |
| 286 | 311 |
| 312 for frame in range(len(supplines)): |
| 313 # Replace the always-changing anonymous namespace prefix with "*". |
| 314 m = re.match("( +fun:)_ZN.*_GLOBAL__N_.*\.cc_" + |
| 315 "[0-9a-fA-F]{8}_[0-9a-fA-F]{8}(.*)", |
| 316 supplines[frame]) |
| 317 if m: |
| 318 supplines[frame] = "*".join(m.groups()) |
| 319 |
| 287 output += "\n".join(supplines) + "\n" | 320 output += "\n".join(supplines) + "\n" |
| 288 | 321 |
| 289 return output | 322 return output |
| 290 | 323 |
| 291 def UniqueString(self): | 324 def UniqueString(self): |
| 292 ''' String to use for object identity. Don't print this, use str(obj) | 325 ''' String to use for object identity. Don't print this, use str(obj) |
| 293 instead.''' | 326 instead.''' |
| 294 rep = self._kind + " " | 327 rep = self._kind + " " |
| 295 for backtrace in self._backtraces: | 328 for backtrace in self._backtraces: |
| 296 for frame in backtrace[1]: | 329 for frame in backtrace[1]: |
| (...skipping 284 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 581 parser.error("no filename specified") | 614 parser.error("no filename specified") |
| 582 filenames = args | 615 filenames = args |
| 583 | 616 |
| 584 analyzer = MemcheckAnalyzer(options.source_dir, use_gdb=True) | 617 analyzer = MemcheckAnalyzer(options.source_dir, use_gdb=True) |
| 585 retcode = analyzer.Report(filenames) | 618 retcode = analyzer.Report(filenames) |
| 586 | 619 |
| 587 sys.exit(retcode) | 620 sys.exit(retcode) |
| 588 | 621 |
| 589 if __name__ == "__main__": | 622 if __name__ == "__main__": |
| 590 _main() | 623 _main() |
| OLD | NEW |