valgrind_tools.py revision 5f1c94371a64b3196d4be9466099bb892df9b88e
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.
4
5"""
6Classes in this file define additional actions that need to be taken to run a
7test under some kind of runtime error detection tool.
8
9The interface is intended to be used as follows.
10
111. For tests that simply run a native process (i.e. no activity is spawned):
12
13Call tool.CopyFiles().
14Prepend test command line with tool.GetTestWrapper().
15
162. For tests that spawn an activity:
17
18Call tool.CopyFiles().
19Call tool.SetupEnvironment().
20Run the test as usual.
21Call tool.CleanUpEnvironment().
22"""
23# pylint: disable=R0201
24
25import glob
26import logging
27import os.path
28import subprocess
29import sys
30
31from pylib.constants import DIR_SOURCE_ROOT
32from pylib.device import device_errors
33
34
35def SetChromeTimeoutScale(device, scale):
36  """Sets the timeout scale in /data/local/tmp/chrome_timeout_scale to scale."""
37  path = '/data/local/tmp/chrome_timeout_scale'
38  if not scale or scale == 1.0:
39    # Delete if scale is None/0.0/1.0 since the default timeout scale is 1.0
40    device.RunShellCommand('rm %s' % path)
41  else:
42    device.WriteFile(path, '%f' % scale, as_root=True)
43
44
45class BaseTool(object):
46  """A tool that does nothing."""
47
48  def __init__(self):
49    """Does nothing."""
50    pass
51
52  def GetTestWrapper(self):
53    """Returns a string that is to be prepended to the test command line."""
54    return ''
55
56  def GetUtilWrapper(self):
57    """Returns the wrapper name for the utilities.
58
59    Returns:
60       A string that is to be prepended to the command line of utility
61    processes (forwarder, etc.).
62    """
63    return ''
64
65  def CopyFiles(self):
66    """Copies tool-specific files to the device, create directories, etc."""
67    pass
68
69  def SetupEnvironment(self):
70    """Sets up the system environment for a test.
71
72    This is a good place to set system properties.
73    """
74    pass
75
76  def CleanUpEnvironment(self):
77    """Cleans up environment."""
78    pass
79
80  def GetTimeoutScale(self):
81    """Returns a multiplier that should be applied to timeout values."""
82    return 1.0
83
84  def NeedsDebugInfo(self):
85    """Whether this tool requires debug info.
86
87    Returns:
88      True if this tool can not work with stripped binaries.
89    """
90    return False
91
92
93class AddressSanitizerTool(BaseTool):
94  """AddressSanitizer tool."""
95
96  WRAPPER_NAME = '/system/bin/asanwrapper'
97  # Disable memcmp overlap check.There are blobs (gl drivers)
98  # on some android devices that use memcmp on overlapping regions,
99  # nothing we can do about that.
100  EXTRA_OPTIONS = 'strict_memcmp=0,use_sigaltstack=1'
101
102  def __init__(self, device):
103    super(AddressSanitizerTool, self).__init__()
104    self._device = device
105    # Configure AndroidCommands to run utils (such as md5sum_bin) under ASan.
106    # This is required because ASan is a compiler-based tool, and md5sum
107    # includes instrumented code from base.
108    device.old_interface.SetUtilWrapper(self.GetUtilWrapper())
109    libs = glob.glob(os.path.join(DIR_SOURCE_ROOT,
110                                  'third_party/llvm-build/Release+Asserts/',
111                                  'lib/clang/*/lib/linux/',
112                                  'libclang_rt.asan-arm-android.so'))
113    assert len(libs) == 1
114    self._lib = libs[0]
115
116  def CopyFiles(self):
117    """Copies ASan tools to the device."""
118    subprocess.call([os.path.join(DIR_SOURCE_ROOT,
119                                  'tools/android/asan/asan_device_setup.sh'),
120                     '--device', str(self._device),
121                     '--lib', self._lib,
122                     '--extra-options', AddressSanitizerTool.EXTRA_OPTIONS])
123    self._device.WaitUntilFullyBooted()
124
125  def GetTestWrapper(self):
126    return AddressSanitizerTool.WRAPPER_NAME
127
128  def GetUtilWrapper(self):
129    """Returns the wrapper for utilities, such as forwarder.
130
131    AddressSanitizer wrapper must be added to all instrumented binaries,
132    including forwarder and the like. This can be removed if such binaries
133    were built without instrumentation. """
134    return self.GetTestWrapper()
135
136  def SetupEnvironment(self):
137    try:
138      self._device.EnableRoot()
139    except device_errors.CommandFailedError as e:
140      # Try to set the timeout scale anyway.
141      # TODO(jbudorick) Handle this exception appropriately after interface
142      #                 conversions are finished.
143      logging.error(str(e))
144    SetChromeTimeoutScale(self._device, self.GetTimeoutScale())
145
146  def CleanUpEnvironment(self):
147    SetChromeTimeoutScale(self._device, None)
148
149  def GetTimeoutScale(self):
150    # Very slow startup.
151    return 20.0
152
153
154class ValgrindTool(BaseTool):
155  """Base abstract class for Valgrind tools."""
156
157  VG_DIR = '/data/local/tmp/valgrind'
158  VGLOGS_DIR = '/data/local/tmp/vglogs'
159
160  def __init__(self, device):
161    super(ValgrindTool, self).__init__()
162    self._device = device
163    # exactly 31 chars, SystemProperties::PROP_NAME_MAX
164    self._wrap_properties = ['wrap.com.google.android.apps.ch',
165                             'wrap.org.chromium.native_test']
166
167  def CopyFiles(self):
168    """Copies Valgrind tools to the device."""
169    self._device.RunShellCommand(
170        'rm -r %s; mkdir %s' % (ValgrindTool.VG_DIR, ValgrindTool.VG_DIR))
171    self._device.RunShellCommand(
172        'rm -r %s; mkdir %s' % (ValgrindTool.VGLOGS_DIR,
173                                ValgrindTool.VGLOGS_DIR))
174    files = self.GetFilesForTool()
175    for f in files:
176      self._device.PushChangedFiles(
177          os.path.join(DIR_SOURCE_ROOT, f),
178          os.path.join(ValgrindTool.VG_DIR, os.path.basename(f)))
179
180  def SetupEnvironment(self):
181    """Sets up device environment."""
182    self._device.RunShellCommand('chmod 777 /data/local/tmp')
183    self._device.RunShellCommand('setenforce 0')
184    for prop in self._wrap_properties:
185      self._device.RunShellCommand(
186          'setprop %s "logwrapper %s"' % (prop, self.GetTestWrapper()))
187    SetChromeTimeoutScale(self._device, self.GetTimeoutScale())
188
189  def CleanUpEnvironment(self):
190    """Cleans up device environment."""
191    for prop in self._wrap_properties:
192      self._device.RunShellCommand('setprop %s ""' % (prop,))
193    SetChromeTimeoutScale(self._device, None)
194
195  def GetFilesForTool(self):
196    """Returns a list of file names for the tool."""
197    raise NotImplementedError()
198
199  def NeedsDebugInfo(self):
200    """Whether this tool requires debug info.
201
202    Returns:
203      True if this tool can not work with stripped binaries.
204    """
205    return True
206
207
208class MemcheckTool(ValgrindTool):
209  """Memcheck tool."""
210
211  def __init__(self, device):
212    super(MemcheckTool, self).__init__(device)
213
214  def GetFilesForTool(self):
215    """Returns a list of file names for the tool."""
216    return ['tools/valgrind/android/vg-chrome-wrapper.sh',
217            'tools/valgrind/memcheck/suppressions.txt',
218            'tools/valgrind/memcheck/suppressions_android.txt']
219
220  def GetTestWrapper(self):
221    """Returns a string that is to be prepended to the test command line."""
222    return ValgrindTool.VG_DIR + '/' + 'vg-chrome-wrapper.sh'
223
224  def GetTimeoutScale(self):
225    """Returns a multiplier that should be applied to timeout values."""
226    return 30
227
228
229class TSanTool(ValgrindTool):
230  """ThreadSanitizer tool. See http://code.google.com/p/data-race-test ."""
231
232  def __init__(self, device):
233    super(TSanTool, self).__init__(device)
234
235  def GetFilesForTool(self):
236    """Returns a list of file names for the tool."""
237    return ['tools/valgrind/android/vg-chrome-wrapper-tsan.sh',
238            'tools/valgrind/tsan/suppressions.txt',
239            'tools/valgrind/tsan/suppressions_android.txt',
240            'tools/valgrind/tsan/ignores.txt']
241
242  def GetTestWrapper(self):
243    """Returns a string that is to be prepended to the test command line."""
244    return ValgrindTool.VG_DIR + '/' + 'vg-chrome-wrapper-tsan.sh'
245
246  def GetTimeoutScale(self):
247    """Returns a multiplier that should be applied to timeout values."""
248    return 30.0
249
250
251TOOL_REGISTRY = {
252    'memcheck': MemcheckTool,
253    'memcheck-renderer': MemcheckTool,
254    'tsan': TSanTool,
255    'tsan-renderer': TSanTool,
256    'asan': AddressSanitizerTool,
257}
258
259
260def CreateTool(tool_name, device):
261  """Creates a tool with the specified tool name.
262
263  Args:
264    tool_name: Name of the tool to create.
265    device: A DeviceUtils instance.
266  Returns:
267    A tool for the specified tool_name.
268  """
269  if not tool_name:
270    return BaseTool()
271
272  ctor = TOOL_REGISTRY.get(tool_name)
273  if ctor:
274    return ctor(device)
275  else:
276    print 'Unknown tool %s, available tools: %s' % (
277        tool_name, ', '.join(sorted(TOOL_REGISTRY.keys())))
278    sys.exit(1)
279