1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Runs an exe through Valgrind and puts the intermediate files in a
23 import drmemory_analyze
24 import memcheck_analyze
26 class BaseTool(object):
27 """Abstract class for running dynamic error detection tools.
29 Always subclass this and implement ToolCommand with framework- and
34 temp_parent_dir
= None
35 self
.log_parent_dir
= ""
36 if common
.IsWindows():
37 # gpu process on Windows Vista+ runs at Low Integrity and can only
38 # write to certain directories (http://crbug.com/119131)
40 # TODO(bruening): if scripts die in middle and don't clean up temp
41 # dir, we'll accumulate files in profile dir. should remove
42 # really old files automatically.
43 profile
= os
.getenv("USERPROFILE")
45 self
.log_parent_dir
= profile
+ "\\AppData\\LocalLow\\"
46 if os
.path
.exists(self
.log_parent_dir
):
47 self
.log_parent_dir
= common
.NormalizeWindowsPath(self
.log_parent_dir
)
48 temp_parent_dir
= self
.log_parent_dir
49 # Generated every time (even when overridden)
50 self
.temp_dir
= tempfile
.mkdtemp(prefix
="vg_logs_", dir=temp_parent_dir
)
51 self
.log_dir
= self
.temp_dir
# overridable by --keep_logs
52 self
.option_parser_hooks
= []
53 # TODO(glider): we may not need some of the env vars on some of the
56 "G_SLICE" : "always-malloc",
57 "NSS_DISABLE_UNLOAD" : "1",
58 "NSS_DISABLE_ARENA_FREE_LIST" : "1",
59 "GTEST_DEATH_TEST_USE_FORK": "1",
63 raise NotImplementedError, "This method should be implemented " \
64 "in the tool-specific subclass"
66 def Analyze(self
, check_sanity
=False):
67 raise NotImplementedError, "This method should be implemented " \
68 "in the tool-specific subclass"
70 def RegisterOptionParserHook(self
, hook
):
71 # Frameworks and tools can add their own flags to the parser.
72 self
.option_parser_hooks
.append(hook
)
74 def CreateOptionParser(self
):
75 # Defines Chromium-specific flags.
76 self
._parser
= optparse
.OptionParser("usage: %prog [options] <program to "
78 self
._parser
.disable_interspersed_args()
79 self
._parser
.add_option("-t", "--timeout",
80 dest
="timeout", metavar
="TIMEOUT", default
=10000,
81 help="timeout in seconds for the run (default 10000)")
82 self
._parser
.add_option("", "--build-dir",
83 help="the location of the compiler output")
84 self
._parser
.add_option("", "--source-dir",
85 help="path to top of source tree for this build"
86 "(used to normalize source paths in baseline)")
87 self
._parser
.add_option("", "--gtest_filter", default
="",
88 help="which test case to run")
89 self
._parser
.add_option("", "--gtest_repeat",
90 help="how many times to run each test")
91 self
._parser
.add_option("", "--gtest_print_time", action
="store_true",
93 help="show how long each test takes")
94 self
._parser
.add_option("", "--ignore_exit_code", action
="store_true",
96 help="ignore exit code of the test "
97 "(e.g. test failures)")
98 self
._parser
.add_option("", "--keep_logs", action
="store_true",
100 help="store memory tool logs in the <tool>.logs "
101 "directory instead of /tmp.\nThis can be "
102 "useful for tool developers/maintainers.\n"
103 "Please note that the <tool>.logs directory "
104 "will be clobbered on tool startup.")
106 # To add framework- or tool-specific flags, please add a hook using
107 # RegisterOptionParserHook in the corresponding subclass.
108 # See ValgrindTool for an example.
109 for hook
in self
.option_parser_hooks
:
110 hook(self
, self
._parser
)
112 def ParseArgv(self
, args
):
113 self
.CreateOptionParser()
115 # self._tool_flags will store those tool flags which we don't parse
116 # manually in this script.
117 self
._tool
_flags
= []
120 """ We assume that the first argument not starting with "-" is a program
121 name and all the following flags should be passed to the program.
122 TODO(timurrrr): customize optparse instead
124 while len(args
) > 0 and args
[0][:1] == "-":
128 if self
._parser
.has_option(arg
.split("=")[0]):
131 self
._tool
_flags
+= [arg
]
137 self
._options
, self
._args
= self
._parser
.parse_args(known_args
)
139 self
._timeout
= int(self
._options
.timeout
)
140 self
._source
_dir
= self
._options
.source_dir
141 if self
._options
.keep_logs
:
142 # log_parent_dir has trailing slash if non-empty
143 self
.log_dir
= self
.log_parent_dir
+ "%s.logs" % self
.ToolName()
144 if os
.path
.exists(self
.log_dir
):
145 shutil
.rmtree(self
.log_dir
)
146 os
.mkdir(self
.log_dir
)
147 logging
.info("Logs are in " + self
.log_dir
)
149 self
._ignore
_exit
_code
= self
._options
.ignore_exit_code
150 if self
._options
.gtest_filter
!= "":
151 self
._args
.append("--gtest_filter=%s" % self
._options
.gtest_filter
)
152 if self
._options
.gtest_repeat
:
153 self
._args
.append("--gtest_repeat=%s" % self
._options
.gtest_repeat
)
154 if self
._options
.gtest_print_time
:
155 self
._args
.append("--gtest_print_time")
159 def Setup(self
, args
):
160 return self
.ParseArgv(args
)
162 def ToolCommand(self
):
163 raise NotImplementedError, "This method should be implemented " \
164 "in the tool-specific subclass"
167 # You may override it in the tool-specific subclass
171 """ Execute the app to be tested after successful instrumentation.
172 Full execution command-line provided by subclassers via proc."""
173 logging
.info("starting execution...")
174 proc
= self
.ToolCommand()
175 for var
in self
._env
:
176 common
.PutEnvAndLog(var
, self
._env
[var
])
177 return common
.RunSubprocess(proc
, self
._timeout
)
179 def RunTestsAndAnalyze(self
, check_sanity
):
180 exec_retcode
= self
.Execute()
181 analyze_retcode
= self
.Analyze(check_sanity
)
184 logging
.error("Analyze failed.")
185 logging
.info("Search the log for '[ERROR]' to see the error reports.")
186 return analyze_retcode
189 if self
._ignore
_exit
_code
:
190 logging
.info("Test execution failed, but the exit code is ignored.")
192 logging
.error("Test execution failed.")
195 logging
.info("Test execution completed successfully.")
197 if not analyze_retcode
:
198 logging
.info("Analysis completed successfully.")
202 def Main(self
, args
, check_sanity
, min_runtime_in_seconds
):
203 """Call this to run through the whole process: Setup, Execute, Analyze"""
204 start_time
= datetime
.datetime
.now()
207 retcode
= self
.RunTestsAndAnalyze(check_sanity
)
208 shutil
.rmtree(self
.temp_dir
, ignore_errors
=True)
211 logging
.error("Setup failed")
212 end_time
= datetime
.datetime
.now()
213 runtime_in_seconds
= (end_time
- start_time
).seconds
214 hours
= runtime_in_seconds
/ 3600
215 seconds
= runtime_in_seconds
% 3600
216 minutes
= seconds
/ 60
217 seconds
= seconds
% 60
218 logging
.info("elapsed time: %02d:%02d:%02d" % (hours
, minutes
, seconds
))
219 if (min_runtime_in_seconds
> 0 and
220 runtime_in_seconds
< min_runtime_in_seconds
):
221 logging
.error("Layout tests finished too quickly. "
222 "It should have taken at least %d seconds. "
223 "Something went wrong?" % min_runtime_in_seconds
)
227 def Run(self
, args
, module
, min_runtime_in_seconds
=0):
228 MODULES_TO_SANITY_CHECK
= ["base"]
230 check_sanity
= module
in MODULES_TO_SANITY_CHECK
231 return self
.Main(args
, check_sanity
, min_runtime_in_seconds
)
234 class ValgrindTool(BaseTool
):
235 """Abstract class for running Valgrind tools.
237 Always subclass this and implement ToolSpecificFlags() and
238 ExtendOptionParser() for tool-specific stuff.
241 super(ValgrindTool
, self
).__init
__()
242 self
.RegisterOptionParserHook(ValgrindTool
.ExtendOptionParser
)
245 # Override if tool prefers nonxml output
248 def ExtendOptionParser(self
, parser
):
249 parser
.add_option("", "--suppressions", default
=[],
251 help="path to a valgrind suppression file")
252 parser
.add_option("", "--indirect", action
="store_true",
254 help="set BROWSER_WRAPPER rather than "
255 "running valgrind directly")
256 parser
.add_option("", "--indirect_webkit_layout", action
="store_true",
258 help="set --wrapper rather than running Dr. Memory "
260 parser
.add_option("", "--trace_children", action
="store_true",
262 help="also trace child processes")
263 parser
.add_option("", "--num-callers",
264 dest
="num_callers", default
=30,
265 help="number of callers to show in stack traces")
266 parser
.add_option("", "--generate_dsym", action
="store_true",
268 help="Generate .dSYM file on Mac if needed. Slow!")
270 def Setup(self
, args
):
271 if not BaseTool
.Setup(self
, args
):
274 self
.PrepareForTestMac()
277 def PrepareForTestMac(self
):
278 """Runs dsymutil if needed.
280 Valgrind for Mac OS X requires that debugging information be in a .dSYM
281 bundle generated by dsymutil. It is not currently able to chase DWARF
282 data into .o files like gdb does, so executables without .dSYM bundles or
283 with the Chromium-specific "fake_dsym" bundles generated by
284 build/mac/strip_save_dsym won't give source file and line number
285 information in valgrind.
287 This function will run dsymutil if the .dSYM bundle is missing or if
288 it looks like a fake_dsym. A non-fake dsym that already exists is assumed
291 test_command
= self
._args
[0]
292 dsym_bundle
= self
._args
[0] + '.dSYM'
293 dsym_file
= os
.path
.join(dsym_bundle
, 'Contents', 'Resources', 'DWARF',
294 os
.path
.basename(test_command
))
295 dsym_info_plist
= os
.path
.join(dsym_bundle
, 'Contents', 'Info.plist')
297 needs_dsymutil
= True
298 saved_test_command
= None
300 if os
.path
.exists(dsym_file
) and os
.path
.exists(dsym_info_plist
):
301 # Look for the special fake_dsym tag in dsym_info_plist.
302 dsym_info_plist_contents
= open(dsym_info_plist
).read()
304 if not re
.search('^\s*<key>fake_dsym</key>$', dsym_info_plist_contents
,
306 # fake_dsym is not set, this is a real .dSYM bundle produced by
307 # dsymutil. dsymutil does not need to be run again.
308 needs_dsymutil
= False
310 # fake_dsym is set. dsym_file is a copy of the original test_command
311 # before it was stripped. Copy it back to test_command so that
312 # dsymutil has unstripped input to work with. Move the stripped
313 # test_command out of the way, it will be restored when this is
315 saved_test_command
= test_command
+ '.stripped'
316 os
.rename(test_command
, saved_test_command
)
317 shutil
.copyfile(dsym_file
, test_command
)
318 shutil
.copymode(saved_test_command
, test_command
)
321 if self
._options
.generate_dsym
:
322 # Remove the .dSYM bundle if it exists.
323 shutil
.rmtree(dsym_bundle
, True)
325 dsymutil_command
= ['dsymutil', test_command
]
327 # dsymutil is crazy slow. Ideally we'd have a timeout here,
328 # but common.RunSubprocess' timeout is only checked
329 # after each line of output; dsymutil is silent
330 # until the end, and is then killed, which is silly.
331 common
.RunSubprocess(dsymutil_command
)
333 if saved_test_command
:
334 os
.rename(saved_test_command
, test_command
)
336 logging
.info("No real .dSYM for test_command. Line numbers will "
337 "not be shown. Either tell xcode to generate .dSYM "
338 "file, or use --generate_dsym option to this tool.")
340 def ToolCommand(self
):
341 """Get the valgrind command to run."""
342 # Note that self._args begins with the exe to be run.
343 tool_name
= self
.ToolName()
345 # Construct the valgrind command.
346 if 'CHROME_VALGRIND' in os
.environ
:
347 path
= os
.path
.join(os
.environ
['CHROME_VALGRIND'], "bin", "valgrind")
350 proc
= [path
, "--tool=%s" % tool_name
]
352 proc
+= ["--num-callers=%i" % int(self
._options
.num_callers
)]
354 if self
._options
.trace_children
:
355 proc
+= ["--trace-children=yes"]
356 proc
+= ["--trace-children-skip='*dbus-daemon*'"]
357 proc
+= ["--trace-children-skip='*dbus-launch*'"]
358 proc
+= ["--trace-children-skip='*perl*'"]
359 proc
+= ["--trace-children-skip='*python*'"]
360 # This is really Python, but for some reason Valgrind follows it.
361 proc
+= ["--trace-children-skip='*lsb_release*'"]
363 proc
+= self
.ToolSpecificFlags()
364 proc
+= self
._tool
_flags
366 suppression_count
= 0
367 for suppression_file
in self
._options
.suppressions
:
368 if os
.path
.exists(suppression_file
):
369 suppression_count
+= 1
370 proc
+= ["--suppressions=%s" % suppression_file
]
372 if not suppression_count
:
373 logging
.warning("WARNING: NOT USING SUPPRESSIONS!")
375 logfilename
= self
.log_dir
+ ("/%s." % tool_name
) + "%p"
377 proc
+= ["--xml=yes", "--xml-file=" + logfilename
]
379 proc
+= ["--log-file=" + logfilename
]
381 # The Valgrind command is constructed.
383 # Handle --indirect_webkit_layout separately.
384 if self
._options
.indirect_webkit_layout
:
385 # Need to create the wrapper before modifying |proc|.
386 wrapper
= self
.CreateBrowserWrapper(proc
, webkit
=True)
388 proc
.append("--wrapper")
392 if self
._options
.indirect
:
393 wrapper
= self
.CreateBrowserWrapper(proc
)
394 os
.environ
["BROWSER_WRAPPER"] = wrapper
395 logging
.info('export BROWSER_WRAPPER=' + wrapper
)
400 def ToolSpecificFlags(self
):
401 raise NotImplementedError, "This method should be implemented " \
402 "in the tool-specific subclass"
404 def CreateBrowserWrapper(self
, proc
, webkit
=False):
405 """The program being run invokes Python or something else that can't stand
406 to be valgrinded, and also invokes the Chrome browser. In this case, use a
407 magic wrapper to only valgrind the Chrome browser. Build the wrapper here.
408 Returns the path to the wrapper. It's up to the caller to use the wrapper
411 command
= " ".join(proc
)
412 # Add the PID of the browser wrapper to the logfile names so we can
413 # separate log files for different UI tests at the analyze stage.
414 command
= command
.replace("%p", "$$.%p")
416 (fd
, indirect_fname
) = tempfile
.mkstemp(dir=self
.log_dir
,
417 prefix
="browser_wrapper.",
419 f
= os
.fdopen(fd
, "w")
420 f
.write('#!/bin/bash\n'
421 'echo "Started Valgrind wrapper for this test, PID=$$" >&2\n')
423 f
.write('DIR=`dirname $0`\n'
424 'TESTNAME_FILE=$DIR/testcase.$$.name\n\n')
427 # Webkit layout_tests pass the URL as the first line of stdin.
428 f
.write('tee $TESTNAME_FILE | %s "$@"\n' % command
)
430 # Try to get the test case name by looking at the program arguments.
431 # i.e. Chromium ui_tests used --test-name arg.
432 # TODO(timurrrr): This doesn't handle "--test-name Test.Name"
433 # TODO(timurrrr): ui_tests are dead. Where do we use the non-webkit
434 # wrapper now? browser_tests? What do they do?
435 f
.write('for arg in $@\ndo\n'
436 ' if [[ "$arg" =~ --test-name=(.*) ]]\n then\n'
437 ' echo ${BASH_REMATCH[1]} >$TESTNAME_FILE\n'
440 '%s "$@"\n' % command
)
443 os
.chmod(indirect_fname
, stat
.S_IRUSR|stat
.S_IXUSR
)
444 return indirect_fname
446 def CreateAnalyzer(self
):
447 raise NotImplementedError, "This method should be implemented " \
448 "in the tool-specific subclass"
450 def GetAnalyzeResults(self
, check_sanity
=False):
451 # Glob all the files in the log directory
452 filenames
= glob
.glob(self
.log_dir
+ "/" + self
.ToolName() + ".*")
454 # If we have browser wrapper, the logfiles are named as
455 # "toolname.wrapper_PID.valgrind_PID".
456 # Let's extract the list of wrapper_PIDs and name it ppids
457 ppids
= set([int(f
.split(".")[-2]) \
458 for f
in filenames
if re
.search("\.[0-9]+\.[0-9]+$", f
)])
460 analyzer
= self
.CreateAnalyzer()
462 # Fast path - no browser wrapper was set.
463 return analyzer
.Report(filenames
, None, check_sanity
)
469 f
= open(self
.log_dir
+ ("/testcase.%d.name" % ppid
))
470 testcase_name
= f
.read().strip()
472 wk_layout_prefix
="third_party/WebKit/LayoutTests/"
473 wk_prefix_at
= testcase_name
.rfind(wk_layout_prefix
)
474 if wk_prefix_at
!= -1:
475 testcase_name
= testcase_name
[wk_prefix_at
+ len(wk_layout_prefix
):]
478 print "====================================================="
479 print " Below is the report for valgrind wrapper PID=%d." % ppid
481 print " It was used while running the `%s` test." % testcase_name
483 print " You can find the corresponding test"
484 print " by searching the above log for 'PID=%d'" % ppid
487 ppid_filenames
= [f
for f
in filenames \
488 if re
.search("\.%d\.[0-9]+$" % ppid
, f
)]
489 # check_sanity won't work with browser wrappers
490 assert check_sanity
== False
491 ret |
= analyzer
.Report(ppid_filenames
, testcase_name
)
492 print "====================================================="
497 print "The Valgrind reports are grouped by test names."
498 print "Each test has its PID printed in the log when the test was run"
499 print "and at the beginning of its Valgrind report."
500 print "Hint: you can search for the reports by Ctrl+F -> `=#`"
506 # TODO(timurrrr): Split into a separate file.
507 class Memcheck(ValgrindTool
):
509 Dynamic memory error detector for Linux & Mac
511 http://valgrind.org/info/tools.html#memcheck
515 super(Memcheck
, self
).__init
__()
516 self
.RegisterOptionParserHook(Memcheck
.ExtendOptionParser
)
521 def ExtendOptionParser(self
, parser
):
522 parser
.add_option("--leak-check", "--leak_check", type="string",
523 default
="yes", # --leak-check=yes is equivalent of =full
524 help="perform leak checking at the end of the run")
525 parser
.add_option("", "--show_all_leaks", action
="store_true",
527 help="also show less blatant leaks")
528 parser
.add_option("", "--track_origins", action
="store_true",
530 help="Show whence uninitialized bytes came. 30% slower.")
532 def ToolSpecificFlags(self
):
533 ret
= ["--gen-suppressions=all", "--demangle=no"]
534 ret
+= ["--leak-check=%s" % self
._options
.leak_check
]
536 if self
._options
.show_all_leaks
:
537 ret
+= ["--show-reachable=yes"]
539 ret
+= ["--show-possibly-lost=no"]
541 if self
._options
.track_origins
:
542 ret
+= ["--track-origins=yes"]
544 # TODO(glider): this is a temporary workaround for http://crbug.com/51716
545 # Let's see whether it helps.
547 ret
+= ["--smc-check=all"]
551 def CreateAnalyzer(self
):
552 use_gdb
= common
.IsMac()
553 return memcheck_analyze
.MemcheckAnalyzer(self
._source
_dir
,
554 self
._options
.show_all_leaks
,
557 def Analyze(self
, check_sanity
=False):
558 ret
= self
.GetAnalyzeResults(check_sanity
)
561 logging
.info("Please see http://dev.chromium.org/developers/how-tos/"
562 "using-valgrind for the info on Memcheck/Valgrind")
566 class DrMemory(BaseTool
):
568 Dynamic memory error detector for Windows.
570 http://dev.chromium.org/developers/how-tos/using-drmemory
571 It is not very mature at the moment, some things might not work properly.
574 def __init__(self
, full_mode
, pattern_mode
):
575 super(DrMemory
, self
).__init
__()
576 self
.full_mode
= full_mode
577 self
.pattern_mode
= pattern_mode
578 self
.RegisterOptionParserHook(DrMemory
.ExtendOptionParser
)
583 def ExtendOptionParser(self
, parser
):
584 parser
.add_option("", "--suppressions", default
=[],
586 help="path to a drmemory suppression file")
587 parser
.add_option("", "--follow_python", action
="store_true",
588 default
=False, dest
="follow_python",
589 help="Monitor python child processes. If off, neither "
590 "python children nor any children of python children "
591 "will be monitored.")
592 parser
.add_option("", "--indirect", action
="store_true",
594 help="set BROWSER_WRAPPER rather than "
595 "running Dr. Memory directly on the harness")
596 parser
.add_option("", "--indirect_webkit_layout", action
="store_true",
598 help="set --wrapper rather than running valgrind "
600 parser
.add_option("", "--use_debug", action
="store_true",
601 default
=False, dest
="use_debug",
602 help="Run Dr. Memory debug build")
603 parser
.add_option("", "--trace_children", action
="store_true",
605 help="TODO: default value differs from Valgrind")
607 def ToolCommand(self
):
608 """Get the tool command to run."""
609 # WINHEAP is what Dr. Memory supports as there are issues w/ both
610 # jemalloc (https://github.com/DynamoRIO/drmemory/issues/320) and
611 # tcmalloc (https://github.com/DynamoRIO/drmemory/issues/314)
613 "CHROME_ALLOCATOR" : "WINHEAP",
614 "JSIMD_FORCEMMX" : "1", # https://github.com/DynamoRIO/drmemory/issues/540
616 for k
,v
in add_env
.iteritems():
617 logging
.info("export %s=%s", k
, v
)
620 drmem_cmd
= os
.getenv("DRMEMORY_COMMAND")
622 raise RuntimeError, "Please set DRMEMORY_COMMAND environment variable " \
623 "with the path to drmemory.exe"
624 proc
= drmem_cmd
.split(" ")
626 # By default, don't run python (this will exclude python's children as well)
627 # to reduce runtime. We're not really interested in spending time finding
628 # bugs in the python implementation.
629 # With file-based config we must update the file every time, and
630 # it will affect simultaneous drmem uses by this user. While file-based
631 # config has many advantages, here we may want this-instance-only
632 # (https://github.com/DynamoRIO/drmemory/issues/334).
633 drconfig_cmd
= [ proc
[0].replace("drmemory.exe", "drconfig.exe") ]
634 drconfig_cmd
+= ["-quiet"] # suppress errors about no 64-bit libs
636 if self
._options
.follow_python
:
637 logging
.info("Following python children")
638 # -unreg fails if not already registered so query for that first
639 query_cmd
= drconfig_cmd
+ ["-isreg", "python.exe"]
640 query_proc
= subprocess
.Popen(query_cmd
, stdout
=subprocess
.PIPE
,
642 (query_out
, query_err
) = query_proc
.communicate()
643 if re
.search("exe not registered", query_out
):
644 run_drconfig
= False # all set
646 drconfig_cmd
+= ["-unreg", "python.exe"]
648 logging
.info("Excluding python children")
649 drconfig_cmd
+= ["-reg", "python.exe", "-norun"]
651 drconfig_retcode
= common
.RunSubprocess(drconfig_cmd
, self
._timeout
)
653 logging
.error("Configuring whether to follow python children failed " \
654 "with %d.", drconfig_retcode
)
655 raise RuntimeError, "Configuring python children failed "
657 suppression_count
= 0
658 supp_files
= self
._options
.suppressions
660 supp_files
+= [s
.replace(".txt", "_full.txt") for s
in supp_files
]
661 for suppression_file
in supp_files
:
662 if os
.path
.exists(suppression_file
):
663 suppression_count
+= 1
664 proc
+= ["-suppress", common
.NormalizeWindowsPath(suppression_file
)]
666 if not suppression_count
:
667 logging
.warning("WARNING: NOT USING SUPPRESSIONS!")
669 # Un-comment to dump Dr.Memory events on error
670 #proc += ["-dr_ops", "-dumpcore_mask", "-dr_ops", "0x8bff"]
672 # Un-comment and comment next line to debug Dr.Memory
673 #proc += ["-dr_ops", "-no_hide"]
674 #proc += ["-dr_ops", "-msgbox_mask", "-dr_ops", "15"]
675 #Proc += ["-dr_ops", "-stderr_mask", "-dr_ops", "15"]
676 # Ensure we see messages about Dr. Memory crashing!
677 proc
+= ["-dr_ops", "-stderr_mask", "-dr_ops", "12"]
679 if self
._options
.use_debug
:
682 proc
+= ["-logdir", common
.NormalizeWindowsPath(self
.log_dir
)]
684 if self
.log_parent_dir
:
685 # gpu process on Windows Vista+ runs at Low Integrity and can only
686 # write to certain directories (http://crbug.com/119131)
687 symcache_dir
= os
.path
.join(self
.log_parent_dir
, "drmemory.symcache")
688 elif self
._options
.build_dir
:
689 # The other case is only possible with -t cmdline.
690 # Anyways, if we omit -symcache_dir the -logdir's value is used which
692 symcache_dir
= os
.path
.join(self
._options
.build_dir
, "drmemory.symcache")
694 if not os
.path
.exists(symcache_dir
):
696 os
.mkdir(symcache_dir
)
698 logging
.warning("Can't create symcache dir?")
699 if os
.path
.exists(symcache_dir
):
700 proc
+= ["-symcache_dir", common
.NormalizeWindowsPath(symcache_dir
)]
702 # Use -no_summary to suppress DrMemory's summary and init-time
703 # notifications. We generate our own with drmemory_analyze.py.
704 proc
+= ["-batch", "-no_summary"]
706 # Un-comment to disable interleaved output. Will also suppress error
707 # messages normally printed to stderr.
708 #proc += ["-quiet", "-no_results_to_stderr"]
710 proc
+= ["-callstack_max_frames", "40"]
712 # disable leak scan for now
713 proc
+= ["-no_count_leaks", "-no_leak_scan"]
715 # disable warnings about unaddressable prefetches
716 proc
+= ["-no_check_prefetch"]
718 # crbug.com/413215, no heap mismatch check for Windows release build binary
719 if common
.IsWindows() and "Release" in self
._options
.build_dir
:
720 proc
+= ["-no_check_delete_mismatch"]
722 # make callstacks easier to read
723 proc
+= ["-callstack_srcfile_prefix",
724 "build\\src,chromium\\src,crt_build\\self_x86"]
725 proc
+= ["-callstack_modname_hide",
726 "*drmemory*,chrome.dll"]
728 boring_callers
= common
.BoringCallers(mangled
=False, use_re_wildcards
=False)
729 # TODO(timurrrr): In fact, we want "starting from .." instead of "below .."
730 proc
+= ["-callstack_truncate_below", ",".join(boring_callers
)]
732 if self
.pattern_mode
:
733 proc
+= ["-pattern", "0xf1fd", "-no_count_leaks", "-redzone_size", "0x20"]
734 elif not self
.full_mode
:
737 proc
+= self
._tool
_flags
739 # Dr.Memory requires -- to separate tool flags from the executable name.
742 if self
._options
.indirect
or self
._options
.indirect_webkit_layout
:
743 wrapper_path
= os
.path
.join(self
._source
_dir
,
744 "tools", "valgrind", "browser_wrapper_win.py")
745 wrapper
= " ".join(["python", wrapper_path
] + proc
)
746 self
.CreateBrowserWrapper(wrapper
)
747 logging
.info("browser wrapper = " + " ".join(proc
))
748 if self
._options
.indirect_webkit_layout
:
750 # Layout tests want forward slashes.
751 wrapper
= wrapper
.replace('\\', '/')
752 proc
+= ["--wrapper", wrapper
]
757 # Note that self._args begins with the name of the exe to be run.
758 self
._args
[0] = common
.NormalizeWindowsPath(self
._args
[0])
762 def CreateBrowserWrapper(self
, command
):
763 os
.putenv("BROWSER_WRAPPER", command
)
765 def Analyze(self
, check_sanity
=False):
766 # Use one analyzer for all the log files to avoid printing duplicate reports
768 # TODO(timurrrr): unify this with Valgrind and other tools when we have
769 # https://github.com/DynamoRIO/drmemory/issues/684
770 analyzer
= drmemory_analyze
.DrMemoryAnalyzer()
773 if not self
._options
.indirect
and not self
._options
.indirect_webkit_layout
:
774 filenames
= glob
.glob(self
.log_dir
+ "/*/results.txt")
776 ret
= analyzer
.Report(filenames
, None, check_sanity
)
778 testcases
= glob
.glob(self
.log_dir
+ "/testcase.*.logs")
779 # If we have browser wrapper, the per-test logdirs are named as
780 # "testcase.wrapper_PID.name".
781 # Let's extract the list of wrapper_PIDs and name it ppids.
782 # NOTE: ppids may contain '_', i.e. they are not ints!
783 ppids
= set([f
.split(".")[-2] for f
in testcases
])
788 f
= open("%s/testcase.%s.name" % (self
.log_dir
, ppid
))
789 testcase_name
= f
.read().strip()
793 print "====================================================="
794 print " Below is the report for drmemory wrapper PID=%s." % ppid
796 print " It was used while running the `%s` test." % testcase_name
798 # TODO(timurrrr): hm, the PID line is suppressed on Windows...
799 print " You can find the corresponding test"
800 print " by searching the above log for 'PID=%s'" % ppid
802 ppid_filenames
= glob
.glob("%s/testcase.%s.logs/*/results.txt" %
803 (self
.log_dir
, ppid
))
804 ret |
= analyzer
.Report(ppid_filenames
, testcase_name
, False)
805 print "====================================================="
808 logging
.info("Please see http://dev.chromium.org/developers/how-tos/"
809 "using-drmemory for the info on Dr. Memory")
814 def Create(self
, tool_name
):
815 if tool_name
== "memcheck":
817 if tool_name
== "drmemory" or tool_name
== "drmemory_light":
818 # TODO(timurrrr): remove support for "drmemory" when buildbots are
819 # switched to drmemory_light OR make drmemory==drmemory_full the default
820 # mode when the tool is mature enough.
821 return DrMemory(False, False)
822 if tool_name
== "drmemory_full":
823 return DrMemory(True, False)
824 if tool_name
== "drmemory_pattern":
825 return DrMemory(False, True)
827 platform_name
= common
.PlatformNames()[0]
828 except common
.NotImplementedError:
829 platform_name
= sys
.platform
+ "(Unknown)"
830 raise RuntimeError, "Unknown tool (tool=%s, platform=%s)" % (tool_name
,
833 def CreateTool(tool
):
834 return ToolFactory().Create(tool
)