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