0

Revert "Reland "Android: Switch from asan_device_setup.py -> wrap.sh (requires O MR1)""

This reverts commit a3be74b7ad.

Reason for revert: Didn't actually disable lint :/

Original change's description:
> Reland "Android: Switch from asan_device_setup.py -> wrap.sh (requires O MR1)"
>
> This reverts commit f3e1cfa3aa.
>
> Reason for reland: Disabling Android lint when ASAN is enabled
>
> Original change's description:
> > Revert "Android: Switch from asan_device_setup.py -> wrap.sh (requires O MR1)"
> >
> > This reverts commit 84403489f3.
> >
> > Reason for revert: Lint errors breaking ASAN compiles
> >
> > Original change's description:
> > > Android: Switch from asan_device_setup.py -> wrap.sh (requires O MR1)
> > >
> > > This bumps the minSdkVersion required for running with is_asan=true to
> > > O MR1, but the new mechanism does not require modifying any global state
> > > on the device, so is much safer to use.
> > >
> > > Removes the --tool arg from test_runner.py, since the test runner no
> > > longer needs to do anything extra for asan to work. The one thing it
> > > does still require is to increase the timeout scale, so this changes to
> > > using --timeout-scale directly instead.
> > >
> > > Also changes ScalableTimeout to being set via instrumentation argument
> > > instead of reading its value from /data/local/tmp.
> > >
> > > And removes @TimeoutScale annotation, which was used only by a single
> > > test in order to extend the timeout of @Manual tests.
> > >
> > > Finally, this extends the timeout for @Manual tests from 10 hours to
> > > 1000 hours... because why would we want a timeout on a @Manual test?
> > >
> > > Bug: 333709824
> > > Change-Id: I4461f05df9143695a8d060f40d9159b45d297cdd
> > > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5473125
> > > Reviewed-by: Haiyang Pan <hypan@google.com>
> > > Commit-Queue: Andrew Grieve <agrieve@chromium.org>
> > > Cr-Commit-Position: refs/heads/main@{#1290797}
> >
> > Bug: 333709824, 336378247
> > Change-Id: I9cb676d547c63e5d1dff4f583ff9886d4fe4b068
> > No-Presubmit: true
> > No-Tree-Checks: true
> > No-Try: true
> > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5475102
> > Auto-Submit: Andrew Grieve <agrieve@chromium.org>
> > Commit-Queue: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
> > Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
> > Cr-Commit-Position: refs/heads/main@{#1291018}
>
> Bug: 333709824, 336378247
> Change-Id: If30b954c371a83aa5a30882a54d13f23b2224b7c
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5473570
> Commit-Queue: Haiyang Pan <hypan@google.com>
> Auto-Submit: Andrew Grieve <agrieve@chromium.org>
> Reviewed-by: Haiyang Pan <hypan@google.com>
> Cr-Commit-Position: refs/heads/main@{#1291305}

Bug: 333709824, 336378247
Change-Id: I8808f2e18e6875a140df643b6d1a37fc5f657fbc
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5479338
Auto-Submit: Andrew Grieve <agrieve@chromium.org>
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Commit-Queue: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Cr-Commit-Position: refs/heads/main@{#1291648}
This commit is contained in:
Andrew Grieve
2024-04-24 01:31:09 +00:00
committed by Chromium LUCI CQ
parent f6b1d55505
commit e2dffc992d
25 changed files with 926 additions and 142 deletions

@ -4880,6 +4880,7 @@ if (is_android) {
"test/android/javatests/src/org/chromium/base/test/util/SkipCheck.java",
"test/android/javatests/src/org/chromium/base/test/util/TestFileUtil.java",
"test/android/javatests/src/org/chromium/base/test/util/TestThreadUtils.java",
"test/android/javatests/src/org/chromium/base/test/util/TimeoutScale.java",
"test/android/javatests/src/org/chromium/base/test/util/TimeoutTimer.java",
"test/android/javatests/src/org/chromium/base/test/util/UserActionTester.java",
"test/android/javatests/src/org/chromium/base/test/util/ViewActionOnDescendant.java",

@ -39,7 +39,6 @@ import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.InMemorySharedPreferencesContext;
import org.chromium.base.test.util.MinAndroidSdkLevel;
import org.chromium.base.test.util.ScalableTimeout;
import org.chromium.build.BuildConfig;
import org.chromium.testing.TestListInstrumentationRunListener;
@ -68,7 +67,6 @@ public class BaseChromiumAndroidJUnitRunner extends AndroidJUnitRunner {
private static final String IS_UNIT_TEST_FLAG = "BaseChromiumAndroidJUnitRunner.IsUnitTest";
private static final String EXTRA_CLANG_COVERAGE_DEVICE_FILE =
"BaseChromiumAndroidJUnitRunner.ClangCoverageDeviceFile";
private static final String EXTRA_TIMEOUT_SCALE = "BaseChromiumAndroidJUnitRunner.TimeoutScale";
private static final String EXTRA_TRACE_FILE = "BaseChromiumAndroidJUnitRunner.TraceFile";
private static final String ARGUMENT_LOG_ONLY = "log";
@ -151,10 +149,6 @@ public class BaseChromiumAndroidJUnitRunner extends AndroidJUnitRunner {
@Override
public void onStart() {
Bundle arguments = InstrumentationRegistry.getArguments();
String timeoutScale = arguments.getString(EXTRA_TIMEOUT_SCALE);
if (timeoutScale != null) {
ScalableTimeout.setScale(Float.valueOf(timeoutScale));
}
if (sTestListMode) {
Log.w(
TAG,

@ -6,17 +6,24 @@ package org.chromium.base.test.util;
/**
* Utility class for scaling various timeouts by a common factor.
*
* <p>Set this value via command-line. E.g.: out/Debug/bin/run_tests --timeout-scale=3
* For example, to run tests under slow memory tools, you might do
* something like this:
* adb shell "echo 20.0 > /data/local/tmp/chrome_timeout_scale"
*/
public class ScalableTimeout {
private static float sTimeoutScale = 1;
public static void setScale(float value) {
sTimeoutScale = value;
}
private static Double sTimeoutScale;
public static final String PROPERTY_FILE = "/data/local/tmp/chrome_timeout_scale";
public static long scaleTimeout(long timeout) {
if (sTimeoutScale == null) {
try {
char[] data = TestFileUtil.readUtf8File(PROPERTY_FILE, 32);
sTimeoutScale = Double.parseDouble(new String(data));
} catch (Exception e) {
// NumberFormatException, FileNotFoundException, IOException
sTimeoutScale = 1.0;
}
}
return (long) (timeout * sTimeoutScale);
}
}

@ -0,0 +1,20 @@
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.base.test.util;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** This annotation can be used to scale a specific test timeout. */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TimeoutScale {
/**
* @return A number to scale the test timeout.
*/
public int value();
}

@ -93,18 +93,6 @@ if (enable_java_templates) {
}
}
if (defined(sanitizer_arch)) {
action("generate_wrap_sh") {
script = "generate_wrap_sh.py"
outputs = [ "$target_gen_dir/$target_name/wrap.sh" ]
args = [
"--arch=$sanitizer_arch",
"--output",
rebase_path(outputs[0], root_build_dir),
]
}
}
# TODO(go/turn-down-test-results): Remove once we turn down
# test-results.appspot.com
python_library("test_result_presentations_py") {
@ -207,6 +195,9 @@ group("test_runner_device_support") {
if (enable_chrome_android_internal) {
data += [ "//clank/tools/android/avd/proto/" ]
}
if (is_asan) {
data_deps += [ "//tools/android/asan/third_party:asan_device_setup" ]
}
if (use_full_mte) {
data_deps += [ "//tools/android/mte:mte_device_setup" ]
}

@ -1,37 +0,0 @@
#! /usr/bin/env python3
# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import pathlib
# Disable memcmp overlap check.There are blobs (gl drivers)
# on some android devices that use memcmp on overlapping regions,
# nothing we can do about that.
#EXTRA_OPTIONS = 'strict_memcmp=0,use_sigaltstack=1'
def _generate(arch):
return f"""\
#!/system/bin/sh
# See: https://github.com/google/sanitizers/wiki/AddressSanitizerOnAndroid/\
01f8df1ac1a447a8475cdfcb03e8b13140042dbd#running-with-wrapsh-recommended
HERE="$(cd "$(dirname "$0")" && pwd)"
log "Launching with ASAN enabled: $0 $@"
export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1
export LD_PRELOAD=$HERE/libclang_rt.asan-{arch}-android.so
exec "$@"
"""
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--arch', required=True)
parser.add_argument('--output', required=True)
args = parser.parse_args()
pathlib.Path(args.output).write_text(_generate(args.arch))
if __name__ == '__main__':
main()

@ -384,9 +384,6 @@ class GtestTestInstance(test_instance.TestInstance):
self._extras = {
_EXTRA_NATIVE_TEST_ACTIVITY: self._apk_helper.GetActivityName(),
}
if args.timeout_scale and args.timeout_scale != 1:
self._extras[_EXTRA_RUN_IN_SUB_THREAD] = 1
if self._suite in RUN_IN_SUB_THREAD_TEST_SUITES:
self._extras[_EXTRA_RUN_IN_SUB_THREAD] = 1
if self._suite in BROWSER_TEST_SUITES:

@ -22,6 +22,10 @@ _INSTRUMENTATION_TEST_INSTANCE_PATH = (
class InstrumentationTestInstanceTest(unittest.TestCase):
def setUp(self):
options = mock.Mock()
options.tool = ''
@staticmethod
def createTestInstance():
c = _INSTRUMENTATION_TEST_INSTANCE_PATH % 'InstrumentationTestInstance'

@ -108,6 +108,7 @@ class LocalDeviceEnvironment(environment.Environment):
self._preferred_abis = None
self._recover_devices = args.recover_devices
self._skip_clear_data = args.skip_clear_data
self._tool_name = args.tool
self._trace_output = None
# Must check if arg exist because this class is used by
# //third_party/catapult's browser_options.py
@ -254,6 +255,10 @@ class LocalDeviceEnvironment(environment.Environment):
def skip_clear_data(self):
return self._skip_clear_data
@property
def tool(self):
return self._tool_name
@property
def trace_output(self):
return self._trace_output

@ -358,7 +358,12 @@ class _ExeDelegate:
return constants.TEST_EXECUTABLE_DIR
def Run(self, test, device, flags=None, **kwargs):
cmd = [posixpath.join(self._device_dist_dir, self._exe_file_name)]
tool = self._test_run.GetTool(device).GetTestWrapper()
if tool:
cmd = [tool]
else:
cmd = []
cmd.append(posixpath.join(self._device_dist_dir, self._exe_file_name))
if test:
cmd.append('--gtest_filter=%s' % ':'.join(test))
@ -368,8 +373,7 @@ class _ExeDelegate:
cwd = constants.TEST_EXECUTABLE_DIR
env = {
'LD_LIBRARY_PATH': self._device_dist_dir,
'UBSAN_OPTIONS': constants.UBSAN_OPTIONS,
'LD_LIBRARY_PATH': self._device_dist_dir
}
if self._coverage_dir:
@ -379,6 +383,8 @@ class _ExeDelegate:
device_coverage_dir, self._suite, self._coverage_index)
self._coverage_index += 1
if self._env.tool != 'asan':
env['UBSAN_OPTIONS'] = constants.UBSAN_OPTIONS
try:
gcov_strip_depth = os.environ['NATIVE_COVERAGE_DEPTH_STRIP']
@ -478,7 +484,11 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
check_return=True,
as_root=self._env.force_main_user)
def start_servers(dev):
def init_tool_and_start_servers(dev):
tool = self.GetTool(dev)
tool.CopyFiles(dev)
tool.SetupEnvironment()
if self._env.disable_test_server:
logging.warning('Not starting test server. Some tests may fail.')
return
@ -502,7 +512,7 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
if self.TestPackage() in _SUITE_REQUIRES_TEST_SERVER_SPAWNER:
self._servers[str(dev)].append(
local_test_server_spawner.LocalTestServerSpawner(
ports.AllocateTestServerPort(), dev))
ports.AllocateTestServerPort(), dev, tool))
for s in self._servers[str(dev)]:
s.SetUp()
@ -512,8 +522,7 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
steps = [
bind_crash_handler(s, device)
for s in (install_apk, push_test_data, start_servers)
]
for s in (install_apk, push_test_data, init_tool_and_start_servers)]
if self._env.concurrent_adb:
reraiser_thread.RunAsync(steps)
else:
@ -761,7 +770,9 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
#override
def _RunTest(self, device, test):
# Run the test.
timeout = self._test_instance.shard_timeout * _GetDeviceTimeoutMultiplier()
timeout = (self._test_instance.shard_timeout *
self.GetTool(device).GetTimeoutScale() *
_GetDeviceTimeoutMultiplier())
if self._test_instance.wait_for_java_debugger:
timeout = None
if self._test_instance.store_tombstones:
@ -955,4 +966,7 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
for s in self._servers.get(str(dev), []):
s.TearDown()
tool = self.GetTool(dev)
tool.CleanUpEnvironment()
self._env.parallel_devices.pMap(individual_device_tear_down)

@ -30,6 +30,7 @@ from devil.android.tools import webview_app
from devil.utils import reraiser_thread
from incremental_install import installer
from pylib import constants
from pylib import valgrind_tools
from pylib.base import base_test_result
from pylib.base import output_manager
from pylib.constants import host_paths
@ -69,7 +70,7 @@ _WPR_GO_LINUX_X86_64_PATH = os.path.join(host_paths.DIR_SOURCE_ROOT,
_TAG = 'test_runner_py'
TIMEOUT_ANNOTATIONS = [
('Manual', 1000 * 60 * 60),
('Manual', 10 * 60 * 60),
('IntegrationTest', 10 * 60),
('External', 10 * 60),
('EnormousTest', 5 * 60),
@ -95,8 +96,6 @@ EXTRA_CLANG_COVERAGE_DEVICE_FILE = (
EXTRA_SCREENSHOT_FILE = (
'org.chromium.base.test.ScreenshotOnFailureStatement.ScreenshotFile')
EXTRA_TIMEOUT_SCALE = 'BaseChromiumAndroidJUnitRunner.TimeoutScale'
EXTRA_UI_CAPTURE_DIR = (
'org.chromium.base.test.util.Screenshooter.ScreenshotDir')
@ -599,6 +598,9 @@ class LocalDeviceInstrumentationTestRun(
logging.debug('Attempting to set WebView flags: %r', webview_flags)
self._webview_flag_changers[str(dev)].AddFlags(webview_flags)
valgrind_tools.SetChromeTimeoutScale(
dev, self._test_instance.timeout_scale)
install_steps += [push_test_data, create_flag_changer]
post_install_steps += [
set_debug_app, approve_app_links, set_vega_permissions,
@ -687,6 +689,8 @@ class LocalDeviceInstrumentationTestRun(
logging.info('Running custom teardown shell command: %s', cmd)
dev.RunShellCommand(cmd, shell=True, check_return=True)
valgrind_tools.SetChromeTimeoutScale(dev, None)
# If we've force approved app links for a package, undo that now.
self._ToggleAppLinks(dev, 'STATE_NO_RESPONSE')
@ -906,6 +910,7 @@ class LocalDeviceInstrumentationTestRun(
extras[_EXTRA_PACKAGE_UNDER_TEST] = package_name
flags_to_add = []
test_timeout_scale = None
if self._test_instance.coverage_directory:
coverage_basename = '%s' % ('%s_%s_group' %
(test[0]['class'], test[0]['method'])
@ -1014,10 +1019,11 @@ class LocalDeviceInstrumentationTestRun(
timeout = FIXED_TEST_TIMEOUT_OVERHEAD + self._GetTimeoutFromAnnotations(
test['annotations'], test_display_name)
timeout_scale = self._test_instance.timeout_scale * (
self._GetTimeoutScaleFromAnnotations(test['annotations']))
if timeout_scale != 1:
extras[EXTRA_TIMEOUT_SCALE] = str(self._test_instance.timeout_scale)
test_timeout_scale = self._GetTimeoutScaleFromAnnotations(
test['annotations'])
if test_timeout_scale and test_timeout_scale != 1:
valgrind_tools.SetChromeTimeoutScale(
device, test_timeout_scale * self._test_instance.timeout_scale)
if self._test_instance.wait_for_java_debugger:
timeout = None
@ -1100,6 +1106,11 @@ class LocalDeviceInstrumentationTestRun(
if flags_to_add:
self._flag_changers[str(device)].Restore()
def restore_timeout_scale():
if test_timeout_scale:
valgrind_tools.SetChromeTimeoutScale(
device, self._test_instance.timeout_scale)
def handle_coverage_data():
if self._test_instance.coverage_directory:
try:
@ -1214,9 +1225,9 @@ class LocalDeviceInstrumentationTestRun(
# the results! Things such as whether the test CRASHED have not yet been
# determined.
post_test_steps = [
restore_flags, stop_chrome_proxy, handle_coverage_data,
handle_render_test_data, pull_ui_screen_captures,
pull_baseline_profile
restore_flags, restore_timeout_scale, stop_chrome_proxy,
handle_coverage_data, handle_render_test_data,
pull_ui_screen_captures, pull_baseline_profile
]
if self._env.concurrent_adb:
reraiser_thread.RunAsync(post_test_steps)
@ -1380,9 +1391,6 @@ class LocalDeviceInstrumentationTestRun(
# Workaround for https://github.com/mockito/mockito/issues/922
'notPackage': 'net.bytebuddy',
}
if self._test_instance.timeout_scale != 1:
extras[EXTRA_TIMEOUT_SCALE] = str(self._test_instance.timeout_scale)
# BaseChromiumAndroidJUnitRunner ignores this bundle value (and always
# adds the listener). This is needed to enable the the listener when
# using AndroidJUnitRunner directly.

@ -19,6 +19,7 @@ from devil.android import device_errors
from devil.android.sdk import version_codes
from devil.android.tools import device_recovery
from devil.utils import signal_handler
from pylib import valgrind_tools
from pylib.base import base_test_result
from pylib.base import test_collection
from pylib.base import test_exception
@ -47,6 +48,7 @@ class LocalDeviceTestRun(test_run.TestRun):
def __init__(self, env, test_instance):
super().__init__(env, test_instance)
self._tools = {}
# This is intended to be filled by a child class.
self._installed_packages = []
env.SetPreferredAbis(test_instance.GetPreferredAbis())
@ -358,6 +360,12 @@ class LocalDeviceTestRun(test_run.TestRun):
return ('Batch' not in annotations
or annotations['Batch']['value'] != 'UnitTests')
def GetTool(self, device):
if str(device) not in self._tools:
self._tools[str(device)] = valgrind_tools.CreateTool(
self._env.tool, device)
return self._tools[str(device)]
def _CreateShardsForDevices(self, tests):
raise NotImplementedError

@ -35,12 +35,12 @@ def _WaitUntil(predicate, max_attempts=5):
class PortForwarderAndroid(chrome_test_server_spawner.PortForwarder):
def __init__(self, device):
def __init__(self, device, tool):
self.device = device
self.tool = tool
def Map(self, port_pairs):
forwarder.Forwarder.Map(port_pairs, self.device)
forwarder.Forwarder.Map(port_pairs, self.device, self.tool)
def GetDevicePortForHostPort(self, host_port):
return forwarder.Forwarder.DevicePortForHostPort(host_port)
@ -60,11 +60,12 @@ class PortForwarderAndroid(chrome_test_server_spawner.PortForwarder):
class LocalTestServerSpawner(test_server.TestServer):
def __init__(self, port, device):
def __init__(self, port, device, tool):
super().__init__()
self._device = device
self._spawning_server = chrome_test_server_spawner.SpawningServer(
port, PortForwarderAndroid(device), MAX_TEST_SERVER_INSTANCES)
port, PortForwarderAndroid(device, tool), MAX_TEST_SERVER_INSTANCES)
self._tool = tool
@property
def server_address(self):
@ -84,7 +85,8 @@ class LocalTestServerSpawner(test_server.TestServer):
self._device.WriteFile(
'%s/net-test-server-config' % self._device.GetExternalStoragePath(),
test_server_config)
forwarder.Forwarder.Map([(self.port, self.port)], self._device)
forwarder.Forwarder.Map(
[(self.port, self.port)], self._device, self._tool)
self._spawning_server.Start()
#override

@ -0,0 +1,116 @@
# Copyright 2012 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# pylint: disable=R0201
import logging
import sys
from devil.android import device_errors
from devil.android.valgrind_tools import base_tool
def SetChromeTimeoutScale(device, scale):
"""Sets the timeout scale in /data/local/tmp/chrome_timeout_scale to scale."""
path = '/data/local/tmp/chrome_timeout_scale'
if not scale or scale == 1.0:
# Delete if scale is None/0.0/1.0 since the default timeout scale is 1.0
device.RemovePath(path, force=True, as_root=True)
else:
device.WriteFile(path, '%f' % scale, as_root=True)
class AddressSanitizerTool(base_tool.BaseTool):
"""AddressSanitizer tool."""
WRAPPER_NAME = '/system/bin/asanwrapper'
# Disable memcmp overlap check.There are blobs (gl drivers)
# on some android devices that use memcmp on overlapping regions,
# nothing we can do about that.
EXTRA_OPTIONS = 'strict_memcmp=0,use_sigaltstack=1'
def __init__(self, device):
super().__init__()
self._device = device
@classmethod
def CopyFiles(cls, device):
"""Copies ASan tools to the device."""
del device
def GetTestWrapper(self):
return AddressSanitizerTool.WRAPPER_NAME
def GetUtilWrapper(self):
"""Returns the wrapper for utilities, such as forwarder.
AddressSanitizer wrapper must be added to all instrumented binaries,
including forwarder and the like. This can be removed if such binaries
were built without instrumentation. """
return self.GetTestWrapper()
def SetupEnvironment(self):
try:
self._device.EnableRoot()
except device_errors.CommandFailedError as e:
# Try to set the timeout scale anyway.
# TODO(jbudorick) Handle this exception appropriately after interface
# conversions are finished.
logging.error(str(e))
SetChromeTimeoutScale(self._device, self.GetTimeoutScale())
def CleanUpEnvironment(self):
SetChromeTimeoutScale(self._device, None)
def GetTimeoutScale(self):
# Very slow startup.
return 20.0
TOOL_REGISTRY = {
'asan': AddressSanitizerTool,
}
def CreateTool(tool_name, device):
"""Creates a tool with the specified tool name.
Args:
tool_name: Name of the tool to create.
device: A DeviceUtils instance.
Returns:
A tool for the specified tool_name.
"""
if not tool_name:
return base_tool.BaseTool()
ctor = TOOL_REGISTRY.get(tool_name)
if ctor:
return ctor(device)
print('Unknown tool %s, available tools: %s' %
(tool_name, ', '.join(sorted(TOOL_REGISTRY.keys()))))
sys.exit(1)
def PushFilesForTool(tool_name, device):
"""Pushes the files required for |tool_name| to |device|.
Args:
tool_name: Name of the tool to create.
device: A DeviceUtils instance.
"""
if not tool_name:
return
clazz = TOOL_REGISTRY.get(tool_name)
if clazz:
clazz.CopyFiles(device)
else:
print('Unknown tool %s, available tools: %s' % (tool_name, ', '.join(
sorted(TOOL_REGISTRY.keys()))))
sys.exit(1)

@ -270,9 +270,6 @@ def AddCommonOptions(parser):
help='If present, store test results on this path.')
parser.add_argument('--isolated-script-test-perf-output',
help='If present, store chartjson results on this path.')
parser.add_argument('--timeout-scale',
type=float,
help='Factor by which timeouts should be scaled.')
AddTestLauncherOptions(parser)
@ -332,7 +329,12 @@ def AddDeviceOptions(parser):
'--recover-devices',
action='store_true',
help='Attempt to recover devices prior to the final retry. Warning: '
'this will cause all devices to reboot.')
'this will cause all devices to reboot.')
parser.add_argument(
'--tool',
dest='tool',
help='Run the test under a tool '
'(use --tool help to list them)')
parser.add_argument(
'--upload-logcats-file',
@ -648,6 +650,10 @@ def AddInstrumentationTestOptions(parser):
help=('Not actually used for instrumentation tests, but can be used as '
'a proxy for determining if the current run is a retry without '
'patch.'))
parser.add_argument(
'--timeout-scale',
type=float,
help='Factor by which timeouts should be scaled.')
parser.add_argument(
'--is-unit-test',
action='store_true',

@ -228,5 +228,6 @@ pylib/utils/logging_utils.py
pylib/utils/repo_utils.py
pylib/utils/test_filter.py
pylib/utils/time_profile.py
pylib/valgrind_tools.py
test_runner.py
tombstones.py

@ -28,7 +28,6 @@ if (is_android || is_chromeos) {
import("//build/config/android/channel.gni")
import("//build/config/clang/clang.gni")
import("//build/config/dcheck_always_on.gni")
import("//build/config/sanitizers/sanitizers.gni")
import("//build/toolchain/siso.gni")
import("//build_overrides/build.gni")
import("abi.gni")
@ -68,9 +67,6 @@ if (is_android || is_chromeos) {
# The default to use for android:minSdkVersion for targets that do
# not explicitly set it.
default_min_sdk_version = 26
if (is_asan) {
default_min_sdk_version = 27
}
# Static analysis can be either "on" or "off" or "build_server". This
# controls how android lint, error-prone, bytecode checks are run. This
@ -93,15 +89,6 @@ if (is_android || is_chromeos) {
# Our build system no longer supports legacy multidex.
min_supported_sdk_version = 21
# ASAN requireds O MR1.
# https://github.com/google/sanitizers/wiki/AddressSanitizerOnAndroid/01f8df1ac1a447a8475cdfcb03e8b13140042dbd#running-with-wrapsh-recommended
if (is_asan) {
min_supported_sdk_version = 27
# Disable lint since increasing min_sdk_version can cause ObsoleteSdkInt warnings.
disable_android_lint = true
}
assert(
default_min_sdk_version >= min_supported_sdk_version,
"default_min_sdk_version ($default_min_sdk_version) must be >= min_supported_sdk_version ($min_supported_sdk_version)")

@ -990,7 +990,7 @@ template("test_runner_script") {
executable_args += [ "--fast-local-dev" ]
}
if (_device_test && is_asan) {
executable_args += [ "--timeout-scale=4" ]
executable_args += [ "--tool=asan" ]
}
if (defined(invoker.modules)) {

@ -33,9 +33,6 @@ if (is_robolectric) {
if (use_cfi_diag || is_ubsan || is_ubsan_security || is_ubsan_vptr) {
_sanitizer_runtimes += [ "$clang_base_path/lib/clang/$clang_version/lib/linux/libclang_rt.ubsan_standalone-$sanitizer_arch-android.so" ]
}
if (is_asan) {
_sanitizer_runtimes += [ "$clang_base_path/lib/clang/$clang_version/lib/linux/libclang_rt.asan-$sanitizer_arch-android.so" ]
}
# Creates a dist directory for a native executable.
#
@ -2106,9 +2103,6 @@ if (is_robolectric) {
if (defined(invoker.min_sdk_version)) {
_min_sdk_version = invoker.min_sdk_version
}
if (is_asan && _min_sdk_version < min_supported_sdk_version) {
_min_sdk_version = min_supported_sdk_version
}
if (defined(invoker.target_sdk_version)) {
_target_sdk_version = invoker.target_sdk_version
}
@ -2586,16 +2580,9 @@ if (is_robolectric) {
if (defined(invoker.loadable_modules)) {
_loadable_modules = invoker.loadable_modules
}
_sanitizer_loadable_modules = []
_sanitizer_deps = []
if (_is_base_module && _native_libs_deps != [] && !_uses_static_library) {
_sanitizer_loadable_modules += _sanitizer_runtimes
}
if (is_asan && _is_base_module &&
(_uses_static_library || _native_libs_deps != [])) {
_sanitizer_loadable_modules +=
[ "$root_gen_dir/build/android/generate_wrap_sh/wrap.sh" ]
_sanitizer_deps += [ "//build/android:generate_wrap_sh" ]
if (_native_libs_deps != []) {
_loadable_modules += _sanitizer_runtimes
}
_assertions_implicitly_enabled = defined(invoker.custom_assertion_handler)
@ -2744,11 +2731,7 @@ if (is_robolectric) {
_secondary_abi_shared_library_list_file
}
if (!defined(deps)) {
deps = []
}
deps += _sanitizer_deps
loadable_modules = _loadable_modules + _sanitizer_loadable_modules
loadable_modules = _loadable_modules
if (defined(_allowlist_r_txt_path) && _is_bundle_module) {
# Used to write the file path to the target's .build_config.json only.
@ -2957,7 +2940,7 @@ if (is_robolectric) {
# Need full deps rather than _non_java_deps, because loadable_modules
# may include .so files extracted by __unpack_aar targets.
deps = _invoker_deps + _sanitizer_deps + [ ":$_build_config_target" ]
deps = _invoker_deps + [ ":$_build_config_target" ]
if (defined(invoker.asset_deps)) {
deps += invoker.asset_deps
}
@ -2976,19 +2959,15 @@ if (is_robolectric) {
# should be clearly named/labeled "incremental".
output_apk_path = _incremental_apk_path
loadable_modules = _sanitizer_loadable_modules
# All native libraries are side-loaded, so use a placeholder to force
# the proper bitness for the app.
_has_native_libs =
defined(_native_libs_filearg) || _loadable_modules != [] ||
_sanitizer_loadable_modules != []
if (_has_native_libs && loadable_modules == [] &&
!defined(native_lib_placeholders)) {
defined(_native_libs_filearg) || _loadable_modules != []
if (_has_native_libs && !defined(native_lib_placeholders)) {
native_lib_placeholders = [ "libfix.crbug.384638.so" ]
}
} else {
loadable_modules = _loadable_modules + _sanitizer_loadable_modules
loadable_modules = _loadable_modules
deps += _all_native_libs_deps + [
":$_compile_resources_target",
":$_merge_manifest_target",
@ -4516,9 +4495,6 @@ if (is_robolectric) {
if (defined(invoker.min_sdk_version)) {
_min_sdk_version = invoker.min_sdk_version
}
if (is_asan && _min_sdk_version < min_supported_sdk_version) {
_min_sdk_version = min_supported_sdk_version
}
_bundle_base_path = "$root_build_dir/apks"
if (defined(invoker.bundle_base_path)) {

@ -200,6 +200,31 @@ is_asan=true
is_debug=false
```
Running ASan applications on Android requires additional device setup. Chromium
testing scripts take care of this, so testing works as expected:
```shell
build/android/test_runner.py instrumentation --test-apk ContentShellTest \
--test_data content:content/test/data/android/device_files -v -v -v \
--tool=asan --release
```
If the above step fails or to run stuff without Chromium testing script (ex.
ContentShell.apk, or any third party apk or binary), device setup is needed:
```shell
tools/android/asan/third_party/asan_device_setup.sh \
--lib third_party/android_toolchain/ndk/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/*/lib/linux
# wait a few seconds for the device to reload
```
It only needs to be run once per device. It is safe to run it multiple times.
Examine the output to ensure that setup was successful (you may need to run
`adb disable-verity` and restart the device first). When this is done, the
device will run ASan apks as well as normal apks without any further setup.
To run command-line tools (i.e. binaries), prefix them with `asanwrapper`:
```shell
adb shell /system/bin/asanwrapper /path/to/binary
```
Use `build/android/asan_symbolize.py` to symbolize stack from `adb logcat`. It
needs the `--output-directory` argument and takes care of translating the device
path to the unstripped binary in the output directory.

@ -3,6 +3,7 @@
# found in the LICENSE file.
import("//base/allocator/partition_allocator/partition_alloc.gni")
import("//build/config/sanitizers/sanitizers.gni")
# Intermediate target grouping the android tools needed to run native
# unittests and instrumentation test apks.
@ -18,6 +19,9 @@ group("android_tools") {
"//tools/perf:run_benchmark_wrapper",
"//tools/perf/clear_system_cache",
]
if (is_asan) {
deps += [ "//tools/android/asan/third_party:asan_device_setup" ]
}
if (use_full_mte) {
deps += [ "//tools/android/mte:mte_device_setup" ]
}

54
tools/android/asan/third_party/BUILD.gn vendored Normal file

@ -0,0 +1,54 @@
# Copyright 2019 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//build/config/android/config.gni")
import("//build/config/clang/clang.gni")
import("//build/util/generate_wrapper.gni")
generate_wrapper("asan_device_setup") {
testonly = true
executable = "with_asan.py"
wrapper_script = "$root_out_dir/bin/run_with_asan"
_lib_archs = []
if (target_cpu == "arm" || target_cpu == "arm64") {
_lib_archs += [
"arm",
"aarch64",
]
} else if (target_cpu == "x86") {
_lib_archs += [ "i686" ]
} else {
assert(false, "No ASAN library available for $target_cpu")
}
_adb_path = "${public_android_sdk_root}/platform-tools/adb"
_lib_dir = "${clang_base_path}/lib/clang/${clang_version}/lib/linux"
_lib_paths = []
foreach(_lib_arch, _lib_archs) {
_lib_paths += [ "${_lib_dir}/libclang_rt.asan-${_lib_arch}-android.so" ]
}
data = [
"asan_device_setup.sh",
"with_asan.py",
_adb_path,
]
data += _lib_paths
data_deps = [
"//build/android:devil_chromium_py",
"//third_party/catapult/devil",
]
_rebased_adb_path = rebase_path(_adb_path, root_build_dir)
_rebased_lib_dir_path = rebase_path(_lib_dir, root_build_dir)
executable_args = [
"--adb",
"@WrappedPath(${_rebased_adb_path})",
"--lib",
"@WrappedPath(${_rebased_lib_dir_path})",
]
}

@ -0,0 +1,8 @@
Name: asan_device_setup.sh
License: Apache 2.0
Version: fbb9132e71a2
URL: https://reviews.llvm.org/source/llvm-github/browse/main/compiler-rt/lib/asan/scripts/asan_device_setup
Security Critical: no
Shipped: no
asan_device_setup.sh is a verbatim copy of asan_device_setup in the LLVM trunk.

@ -0,0 +1,466 @@
#!/bin/bash
#===- lib/asan/scripts/asan_device_setup -----------------------------------===#
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# Prepare Android device to run ASan applications.
#
#===------------------------------------------------------------------------===#
set -e
HERE="$(cd "$(dirname "$0")" && pwd)"
revert=no
extra_options=
device=
lib=
use_su=0
function usage {
echo "usage: $0 [--revert] [--device device-id] [--lib path] [--extra-options options]"
echo " --revert: Uninstall ASan from the device."
echo " --lib: Path to ASan runtime library."
echo " --extra-options: Extra ASAN_OPTIONS."
echo " --device: Install to the given device. Use 'adb devices' to find"
echo " device-id."
echo " --use-su: Use 'su -c' prefix for every adb command instead of using"
echo " 'adb root' once."
echo
exit 1
}
function adb_push {
if [ $use_su -eq 0 ]; then
$ADB push "$1" "$2"
else
local FILENAME=$(basename $1)
$ADB push "$1" "/data/local/tmp/$FILENAME"
$ADB shell su -c "rm \\\"$2/$FILENAME\\\"" >&/dev/null
$ADB shell su -c "cat \\\"/data/local/tmp/$FILENAME\\\" > \\\"$2/$FILENAME\\\""
$ADB shell su -c "rm \\\"/data/local/tmp/$FILENAME\\\""
fi
}
function adb_remount {
if [ $use_su -eq 0 ]; then
$ADB remount
else
local STORAGE=`$ADB shell mount | grep /system | cut -d ' ' -f1`
if [ "$STORAGE" != "" ]; then
echo Remounting $STORAGE at /system
$ADB shell su -c "mount -o rw,remount $STORAGE /system"
else
echo Failed to get storage device name for "/system" mount point
fi
fi
}
function adb_shell {
if [ $use_su -eq 0 ]; then
$ADB shell $@
else
$ADB shell su -c "$*"
fi
}
function adb_root {
if [ $use_su -eq 0 ]; then
$ADB root
fi
}
function adb_wait_for_device {
$ADB wait-for-device
}
function adb_pull {
if [ $use_su -eq 0 ]; then
$ADB pull "$1" "$2"
else
local FILENAME=$(basename $1)
$ADB shell rm "/data/local/tmp/$FILENAME" >&/dev/null
$ADB shell su -c "[ -f \\\"$1\\\" ] && cat \\\"$1\\\" > \\\"/data/local/tmp/$FILENAME\\\" && chown root.shell \\\"/data/local/tmp/$FILENAME\\\" && chmod 755 \\\"/data/local/tmp/$FILENAME\\\"" &&
$ADB pull "/data/local/tmp/$FILENAME" "$2" >&/dev/null && $ADB shell "rm \"/data/local/tmp/$FILENAME\""
fi
}
function get_device_arch { # OUT OUT64
local _outvar=$1
local _outvar64=$2
local _ABI=$(adb_shell getprop ro.product.cpu.abi)
local _ARCH=
local _ARCH64=
if [[ $_ABI == x86* ]]; then
_ARCH=i686
elif [[ $_ABI == armeabi* ]]; then
_ARCH=arm
elif [[ $_ABI == arm64-v8a* ]]; then
_ARCH=arm
_ARCH64=aarch64
else
echo "Unrecognized device ABI: $_ABI"
exit 1
fi
eval $_outvar=\$_ARCH
eval $_outvar64=\$_ARCH64
}
while [[ $# > 0 ]]; do
case $1 in
--revert)
revert=yes
;;
--extra-options)
shift
if [[ $# == 0 ]]; then
echo "--extra-options requires an argument."
exit 1
fi
extra_options="$1"
;;
--lib)
shift
if [[ $# == 0 ]]; then
echo "--lib requires an argument."
exit 1
fi
lib="$1"
;;
--device)
shift
if [[ $# == 0 ]]; then
echo "--device requires an argument."
exit 1
fi
device="$1"
;;
--use-su)
use_su=1
;;
*)
usage
;;
esac
shift
done
ADB=${ADB:-adb}
if [[ x$device != x ]]; then
ADB="$ADB -s $device"
fi
if [ $use_su -eq 1 ]; then
# Test if 'su' is present on the device
SU_TEST_OUT=`$ADB shell su -c "echo foo" 2>&1 | sed 's/\r$//'`
if [ $? != 0 -o "$SU_TEST_OUT" != "foo" ]; then
echo "ERROR: Cannot use 'su -c':"
echo "$ adb shell su -c \"echo foo\""
echo $SU_TEST_OUT
echo "Check that 'su' binary is correctly installed on the device or omit"
echo " --use-su flag"
exit 1
fi
fi
echo '>> Remounting /system rw'
adb_wait_for_device
adb_root
adb_wait_for_device
adb_remount
adb_wait_for_device
get_device_arch ARCH ARCH64
echo "Target architecture: $ARCH"
ASAN_RT="libclang_rt.asan-$ARCH-android.so"
if [[ -n $ARCH64 ]]; then
echo "Target architecture: $ARCH64"
ASAN_RT64="libclang_rt.asan-$ARCH64-android.so"
fi
RELEASE=$(adb_shell getprop ro.build.version.release)
PRE_L=0
if echo "$RELEASE" | grep '^4\.' >&/dev/null; then
PRE_L=1
fi
ANDROID_O=0
if echo "$RELEASE" | grep '^8\.0\.' >&/dev/null; then
# 8.0.x is for Android O
ANDROID_O=1
fi
if [[ x$revert == xyes ]]; then
echo '>> Uninstalling ASan'
if ! adb_shell ls -l /system/bin/app_process | grep -o '\->.*app_process' >&/dev/null; then
echo '>> Pre-L device detected.'
adb_shell mv /system/bin/app_process.real /system/bin/app_process
adb_shell rm /system/bin/asanwrapper
elif ! adb_shell ls -l /system/bin/app_process64.real | grep -o 'No such file or directory' >&/dev/null; then
# 64-bit installation.
adb_shell mv /system/bin/app_process32.real /system/bin/app_process32
adb_shell mv /system/bin/app_process64.real /system/bin/app_process64
adb_shell rm /system/bin/asanwrapper
adb_shell rm /system/bin/asanwrapper64
else
# 32-bit installation.
adb_shell rm /system/bin/app_process.wrap
adb_shell rm /system/bin/asanwrapper
adb_shell rm /system/bin/app_process
adb_shell ln -s /system/bin/app_process32 /system/bin/app_process
fi
if [[ ANDROID_O -eq 1 ]]; then
adb_shell mv /system/etc/ld.config.txt.saved /system/etc/ld.config.txt
fi
echo '>> Restarting shell'
adb_shell stop
adb_shell start
# Remove the library on the last step to give a chance to the 'su' binary to
# be executed without problem.
adb_shell rm /system/lib/$ASAN_RT
echo '>> Done'
exit 0
fi
if [[ -d "$lib" ]]; then
ASAN_RT_PATH="$lib"
elif [[ -f "$lib" && "$lib" == *"$ASAN_RT" ]]; then
ASAN_RT_PATH=$(dirname "$lib")
elif [[ -f "$HERE/$ASAN_RT" ]]; then
ASAN_RT_PATH="$HERE"
elif [[ $(basename "$HERE") == "bin" ]]; then
# We could be in the toolchain's base directory.
# Consider ../lib, ../lib/asan, ../lib/linux,
# ../lib/clang/$VERSION/lib/linux, and ../lib64/clang/$VERSION/lib/linux.
P=$(ls "$HERE"/../lib/"$ASAN_RT" \
"$HERE"/../lib/asan/"$ASAN_RT" \
"$HERE"/../lib/linux/"$ASAN_RT" \
"$HERE"/../lib/clang/*/lib/linux/"$ASAN_RT" \
"$HERE"/../lib64/clang/*/lib/linux/"$ASAN_RT" 2>/dev/null | sort | tail -1)
if [[ -n "$P" ]]; then
ASAN_RT_PATH="$(dirname "$P")"
fi
fi
if [[ -z "$ASAN_RT_PATH" || ! -f "$ASAN_RT_PATH/$ASAN_RT" ]]; then
echo ">> ASan runtime library not found"
exit 1
fi
if [[ -n "$ASAN_RT64" ]]; then
if [[ -z "$ASAN_RT_PATH" || ! -f "$ASAN_RT_PATH/$ASAN_RT64" ]]; then
echo ">> ASan runtime library not found"
exit 1
fi
fi
TMPDIRBASE=$(mktemp -d)
TMPDIROLD="$TMPDIRBASE/old"
TMPDIR="$TMPDIRBASE/new"
mkdir "$TMPDIROLD"
if ! adb_shell ls -l /system/bin/app_process | grep -o '\->.*app_process' >&/dev/null; then
if adb_pull /system/bin/app_process.real /dev/null >&/dev/null; then
echo '>> Old-style ASan installation detected. Reverting.'
adb_shell mv /system/bin/app_process.real /system/bin/app_process
fi
echo '>> Pre-L device detected. Setting up app_process symlink.'
adb_shell mv /system/bin/app_process /system/bin/app_process32
adb_shell ln -s /system/bin/app_process32 /system/bin/app_process
fi
echo '>> Copying files from the device'
if [[ -n "$ASAN_RT64" ]]; then
adb_pull /system/lib/"$ASAN_RT" "$TMPDIROLD" || true
adb_pull /system/lib64/"$ASAN_RT64" "$TMPDIROLD" || true
adb_pull /system/bin/app_process32 "$TMPDIROLD" || true
adb_pull /system/bin/app_process32.real "$TMPDIROLD" || true
adb_pull /system/bin/app_process64 "$TMPDIROLD" || true
adb_pull /system/bin/app_process64.real "$TMPDIROLD" || true
adb_pull /system/bin/asanwrapper "$TMPDIROLD" || true
adb_pull /system/bin/asanwrapper64 "$TMPDIROLD" || true
else
adb_pull /system/lib/"$ASAN_RT" "$TMPDIROLD" || true
adb_pull /system/bin/app_process32 "$TMPDIROLD" || true
adb_pull /system/bin/app_process.wrap "$TMPDIROLD" || true
adb_pull /system/bin/asanwrapper "$TMPDIROLD" || true
fi
cp -r "$TMPDIROLD" "$TMPDIR"
if [[ -f "$TMPDIR/app_process.wrap" || -f "$TMPDIR/app_process64.real" ]]; then
echo ">> Previous installation detected"
else
echo ">> New installation"
fi
echo '>> Generating wrappers'
cp "$ASAN_RT_PATH/$ASAN_RT" "$TMPDIR/"
if [[ -n "$ASAN_RT64" ]]; then
cp "$ASAN_RT_PATH/$ASAN_RT64" "$TMPDIR/"
fi
ASAN_OPTIONS=start_deactivated=1
# The name of a symlink to libclang_rt.asan-$ARCH-android.so used in LD_PRELOAD.
# The idea is to have the same name in lib and lib64 to keep it from falling
# apart when a 64-bit process spawns a 32-bit one, inheriting the environment.
ASAN_RT_SYMLINK=symlink-to-libclang_rt.asan
function generate_zygote_wrapper { # from, to
local _from=$1
local _to=$2
if [[ PRE_L -eq 0 ]]; then
# LD_PRELOAD parsing is broken in N if it starts with ":". Luckily, it is
# unset in the system environment since L.
local _ld_preload=$ASAN_RT_SYMLINK
else
local _ld_preload=\$LD_PRELOAD:$ASAN_RT_SYMLINK
fi
cat <<EOF >"$TMPDIR/$_from"
#!/system/bin/sh-from-zygote
ASAN_OPTIONS=$ASAN_OPTIONS \\
ASAN_ACTIVATION_OPTIONS=include_if_exists=/data/local/tmp/asan.options.%b \\
LD_PRELOAD=$_ld_preload \\
exec $_to "\$@"
EOF
}
# On Android-L not allowing user segv handler breaks some applications.
# Since ~May 2017 this is the default setting; included for compatibility with
# older library versions.
if [[ PRE_L -eq 0 ]]; then
ASAN_OPTIONS="$ASAN_OPTIONS,allow_user_segv_handler=1"
fi
if [[ x$extra_options != x ]] ; then
ASAN_OPTIONS="$ASAN_OPTIONS,$extra_options"
fi
# Zygote wrapper.
if [[ -f "$TMPDIR/app_process64" ]]; then
# A 64-bit device.
if [[ ! -f "$TMPDIR/app_process64.real" ]]; then
# New installation.
mv "$TMPDIR/app_process32" "$TMPDIR/app_process32.real"
mv "$TMPDIR/app_process64" "$TMPDIR/app_process64.real"
fi
generate_zygote_wrapper "app_process32" "/system/bin/app_process32.real"
generate_zygote_wrapper "app_process64" "/system/bin/app_process64.real"
else
# A 32-bit device.
generate_zygote_wrapper "app_process.wrap" "/system/bin/app_process32"
fi
# General command-line tool wrapper (use for anything that's not started as
# zygote).
cat <<EOF >"$TMPDIR/asanwrapper"
#!/system/bin/sh
LD_PRELOAD=$ASAN_RT_SYMLINK \\
exec \$@
EOF
if [[ -n "$ASAN_RT64" ]]; then
cat <<EOF >"$TMPDIR/asanwrapper64"
#!/system/bin/sh
LD_PRELOAD=$ASAN_RT_SYMLINK \\
exec \$@
EOF
fi
function install { # from, to, chmod, chcon
local _from=$1
local _to=$2
local _mode=$3
local _context=$4
local _basename="$(basename "$_from")"
echo "Installing $_to/$_basename $_mode $_context"
adb_push "$_from" "$_to/$_basename"
adb_shell chown root.shell "$_to/$_basename"
if [[ -n "$_mode" ]]; then
adb_shell chmod "$_mode" "$_to/$_basename"
fi
if [[ -n "$_context" ]]; then
adb_shell chcon "$_context" "$_to/$_basename"
fi
}
if ! ( cd "$TMPDIRBASE" && diff -qr old/ new/ ) ; then
# Make SELinux happy by keeping app_process wrapper and the shell
# it runs on in zygote domain.
ENFORCING=0
if adb_shell getenforce | grep Enforcing >/dev/null; then
# Sometimes shell is not allowed to change file contexts.
# Temporarily switch to permissive.
ENFORCING=1
adb_shell setenforce 0
fi
if [[ PRE_L -eq 1 ]]; then
CTX=u:object_r:system_file:s0
else
CTX=u:object_r:zygote_exec:s0
fi
echo '>> Pushing files to the device'
if [[ -n "$ASAN_RT64" ]]; then
install "$TMPDIR/$ASAN_RT" /system/lib 644
install "$TMPDIR/$ASAN_RT64" /system/lib64 644
install "$TMPDIR/app_process32" /system/bin 755 $CTX
install "$TMPDIR/app_process32.real" /system/bin 755 $CTX
install "$TMPDIR/app_process64" /system/bin 755 $CTX
install "$TMPDIR/app_process64.real" /system/bin 755 $CTX
install "$TMPDIR/asanwrapper" /system/bin 755
install "$TMPDIR/asanwrapper64" /system/bin 755
adb_shell rm -f /system/lib/$ASAN_RT_SYMLINK
adb_shell ln -s $ASAN_RT /system/lib/$ASAN_RT_SYMLINK
adb_shell rm -f /system/lib64/$ASAN_RT_SYMLINK
adb_shell ln -s $ASAN_RT64 /system/lib64/$ASAN_RT_SYMLINK
else
install "$TMPDIR/$ASAN_RT" /system/lib 644
install "$TMPDIR/app_process32" /system/bin 755 $CTX
install "$TMPDIR/app_process.wrap" /system/bin 755 $CTX
install "$TMPDIR/asanwrapper" /system/bin 755 $CTX
adb_shell rm -f /system/lib/$ASAN_RT_SYMLINK
adb_shell ln -s $ASAN_RT /system/lib/$ASAN_RT_SYMLINK
adb_shell rm /system/bin/app_process
adb_shell ln -s /system/bin/app_process.wrap /system/bin/app_process
fi
adb_shell cp /system/bin/sh /system/bin/sh-from-zygote
adb_shell chcon $CTX /system/bin/sh-from-zygote
if [[ ANDROID_O -eq 1 ]]; then
# For Android O, the linker namespace is temporarily disabled.
adb_shell mv /system/etc/ld.config.txt /system/etc/ld.config.txt.saved
fi
if [ $ENFORCING == 1 ]; then
adb_shell setenforce 1
fi
echo '>> Restarting shell (asynchronous)'
adb_shell stop
adb_shell start
echo '>> Please wait until the device restarts'
else
echo '>> Device is up to date'
fi
rm -r "$TMPDIRBASE"

127
tools/android/asan/third_party/with_asan.py vendored Executable file

@ -0,0 +1,127 @@
#!/usr/bin/env vpython3
# Copyright 2019 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import contextlib
import logging
import os
import subprocess
import sys
_SRC_ROOT = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', '..', '..', '..'))
sys.path.append(os.path.join(_SRC_ROOT, 'third_party', 'catapult', 'devil'))
from devil import base_error
from devil.android import device_utils
from devil.android.sdk import adb_wrapper
from devil.android.sdk import version_codes
from devil.utils import logging_common
sys.path.append(os.path.join(_SRC_ROOT, 'build', 'android'))
import devil_chromium
_SCRIPT_PATH = os.path.abspath(
os.path.join(
os.path.dirname(__file__),
'asan_device_setup.sh'))
@contextlib.contextmanager
def _LogDevicesOnFailure(msg):
try:
yield
except base_error.BaseError:
logging.exception(msg)
logging.error('Devices visible to adb:')
for entry in adb_wrapper.AdbWrapper.Devices(desired_state=None,
long_list=True):
logging.error(' %s: %s',
entry[0].GetDeviceSerial(),
' '.join(entry[1:]))
raise
@contextlib.contextmanager
def Asan(args):
env = os.environ.copy()
env['ADB'] = args.adb
try:
with _LogDevicesOnFailure('Failed to set up the device.'):
device = device_utils.DeviceUtils.HealthyDevices(
device_arg=args.device)[0]
disable_verity = device.build_version_sdk >= version_codes.MARSHMALLOW
if disable_verity:
device.EnableRoot()
# TODO(crbug.com/790202): Stop logging output after diagnosing
# issues on android-asan.
verity_output = device.adb.DisableVerity()
if verity_output:
logging.info('disable-verity output:')
for line in verity_output.splitlines():
logging.info(' %s', line)
device.Reboot()
# Call EnableRoot prior to asan_device_setup.sh to ensure it doesn't
# get tripped up by the root timeout.
device.EnableRoot()
setup_cmd = [_SCRIPT_PATH, '--lib', args.lib]
if args.device:
setup_cmd += ['--device', args.device]
subprocess.check_call(setup_cmd, env=env)
yield
finally:
with _LogDevicesOnFailure('Failed to tear down the device.'):
device.EnableRoot()
teardown_cmd = [_SCRIPT_PATH, '--revert']
if args.device:
teardown_cmd += ['--device', args.device]
subprocess.check_call(teardown_cmd, env=env)
if disable_verity:
# TODO(crbug.com/790202): Stop logging output after diagnosing
# issues on android-asan.
verity_output = device.adb.EnableVerity()
if verity_output:
logging.info('enable-verity output:')
for line in verity_output.splitlines():
logging.info(' %s', line)
device.Reboot()
def main(raw_args):
parser = argparse.ArgumentParser()
logging_common.AddLoggingArguments(parser)
parser.add_argument(
'--adb', type=os.path.realpath, required=True,
help='Path to adb binary.')
parser.add_argument(
'--device',
help='Device serial.')
parser.add_argument(
'--lib', type=os.path.realpath, required=True,
help='Path to asan library.')
parser.add_argument(
'command', nargs='*',
help='Command to run with ASAN installed.')
args = parser.parse_args()
# TODO(crbug.com/790202): Remove this after diagnosing issues
# with android-asan.
if not args.quiet:
args.verbose += 1
logging_common.InitializeLogging(args)
devil_chromium.Initialize(adb_path=args.adb)
with Asan(args):
if args.command:
return subprocess.call(args.command)
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))