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