Skip to content

Commit

Permalink
Install sanitizer libraries to the out directory.
Browse files Browse the repository at this point in the history
If the app uses any sanitizers, we can install the library to the out
directory to allow them to use wrap.sh rather than root their devices.

This only works for ndk-build due to the nature of CMake toolchain
files.

Test: Removed the sanitizer runtime libraries from my test devices
Test: ./run_tests.py --rebuild --filter asan-smoke
Bug: android/ndk#540
Change-Id: I2944c468a1b34161d792e689c68ad2c391bdd409
(cherry picked from commit d7e5e76ed836ed0c4093d5558d64fabecdfdb1eb)
  • Loading branch information
DanAlbert committed Feb 15, 2018
1 parent 15f3105 commit bd4090b
Show file tree
Hide file tree
Showing 12 changed files with 171 additions and 135 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ __pycache__/

# Distribution / packaging
.Python
/build/
develop-eggs/
dist/
downloads/
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ NDK
of a local development build.
* `ANDROID_NDK_BETA`: The beta version of the NDK. This is 0 for a stable
release.
* ndk-build now installs sanitizer runtime libraries to your out directory for
inclusion in your APK. Coupled with [wrap.sh], this removes the requirement
of rooting your device to use sanitizers. See [Issue 540].

[wrap.sh]: https://developer.android.com/ndk/guides/wrap-script.html
[Issue 540]: https://github.com/android-ndk/ndk/issues/540

[Issue 313]: https://github.com/android-ndk/ndk/issues/313

Expand Down
59 changes: 59 additions & 0 deletions build/core/gdb.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#
# Copyright (C) 2018 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# Ensure that for debuggable applications, gdbserver will be copied to
# the proper location

NDK_APP_GDBSERVER := $(NDK_APP_DST_DIR)/gdbserver
NDK_APP_GDBSETUP := $(NDK_APP_DST_DIR)/gdb.setup

ifeq ($(NDK_APP_DEBUGGABLE),true)
ifeq ($(TARGET_SONAME_EXTENSION),.so)

installed_modules: $(NDK_APP_GDBSERVER)

$(NDK_APP_GDBSERVER): PRIVATE_ABI := $(TARGET_ARCH_ABI)
$(NDK_APP_GDBSERVER): PRIVATE_NAME := $(TOOLCHAIN_NAME)
$(NDK_APP_GDBSERVER): PRIVATE_SRC := $(TARGET_GDBSERVER)
$(NDK_APP_GDBSERVER): PRIVATE_DST := $(NDK_APP_GDBSERVER)

$(call generate-file-dir,$(NDK_APP_GDBSERVER))

$(NDK_APP_GDBSERVER): clean-installed-binaries
$(call host-echo-build-step,$(PRIVATE_ABI),Gdbserver) "[$(PRIVATE_NAME)] $(call pretty-dir,$(PRIVATE_DST))"
$(hide) $(call host-install,$(PRIVATE_SRC),$(PRIVATE_DST))
endif

# Install gdb.setup for both .so and .bc projects
ifneq (,$(filter $(TARGET_SONAME_EXTENSION),.so .bc))
installed_modules: $(NDK_APP_GDBSETUP)

$(NDK_APP_GDBSETUP): PRIVATE_ABI := $(TARGET_ARCH_ABI)
$(NDK_APP_GDBSETUP): PRIVATE_DST := $(NDK_APP_GDBSETUP)
$(NDK_APP_GDBSETUP): PRIVATE_SOLIB_PATH := $(TARGET_OUT)
$(NDK_APP_GDBSETUP): PRIVATE_SRC_DIRS := $(SYSROOT_INC)

$(NDK_APP_GDBSETUP):
$(call host-echo-build-step,$(PRIVATE_ABI),Gdbsetup) "$(call pretty-dir,$(PRIVATE_DST))"
$(hide) $(HOST_ECHO) "set solib-search-path $(call host-path,$(PRIVATE_SOLIB_PATH))" > $(PRIVATE_DST)
$(hide) $(HOST_ECHO) "directory $(call host-path,$(call remove-duplicates,$(PRIVATE_SRC_DIRS)))" >> $(PRIVATE_DST)

$(call generate-file-dir,$(NDK_APP_GDBSETUP))

# This prevents parallel execution to clear gdb.setup after it has been written to
$(NDK_APP_GDBSETUP): clean-installed-binaries
endif
endif
49 changes: 49 additions & 0 deletions build/core/install_sanitizer.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#
# Copyright (C) 2018 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# Generates rules to install sanitizer runtime libraries to the out directory if
# the sanitizer is requested in the app's ldflags.
#
# Args:
# NDK_SANTIZER_NAME:
# Sanitizer name used as the variable name component to find the library.
# i.e. $(TARGET_$(2)_BASENAME) is the name of the library to install. This
# is also used as the name printed to the terminal for the build step.
# NDK_SANITIZER_FSANITIZE_ARGS:
# -fsanitize= arguments that require this runtime library.
#
# Example usage:
# NDK_SANITIZER_NAME := UBSAN
# NDK_SANITIZER_FSANITIZE_ARGS := undefined
# include $(BUILD_SYSTEM)/install_sanitizer.mk

ifneq (,$(filter $(NDK_SANITIZER_FSANITIZE_ARGS),$(NDK_SANITIZERS)))
installed_modules: $(NDK_APP_$(NDK_SANITIZER_NAME))

NDK_SANITIZER_TARGET := $(NDK_APP_$(NDK_SANITIZER_NAME))
NDK_SANITIZER_LIB_PATH := $(NDK_TOOLCHAIN_LIB_DIR)/$(TARGET_$(NDK_SANITIZER_NAME)_BASENAME)

$(NDK_SANITIZER_TARGET): PRIVATE_ABI := $(TARGET_ARCH_ABI)
$(NDK_SANITIZER_TARGET): PRIVATE_NAME := $(NDK_SANITIZER_NAME)
$(NDK_SANITIZER_TARGET): PRIVATE_SRC := $(NDK_SANITIZER_LIB_PATH)
$(NDK_SANITIZER_TARGET): PRIVATE_DST := $(NDK_APP_$(NDK_SANITIZER_NAME))

$(call generate-file-dir,$(NDK_APP_$(NDK_SANITIZER_NAME)))

$(NDK_SANITIZER_TARGET): clean-installed-binaries
$(call host-echo-build-step,$(PRIVATE_ABI),$(PRIVATE_NAME) "$(call pretty-dir,$(PRIVATE_DST))")
$(hide) $(call host-install,$(PRIVATE_SRC),$(PRIVATE_DST))
endif
40 changes: 40 additions & 0 deletions build/core/sanitizers.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#
# Copyright (C) 2018 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

NDK_TOOLCHAIN_LIB_SUFFIX := 64
ifeq ($(HOST_ARCH64),x86)
NDK_TOOLCHAIN_LIB_SUFFIX :=
endif

NDK_TOOLCHAIN_LIB_DIR := \
$(LLVM_TOOLCHAIN_PREBUILT_ROOT)/lib$(NDK_TOOLCHAIN_LIB_SUFFIX)/clang/5.0.2/lib/linux

NDK_APP_ASAN := $(NDK_APP_DST_DIR)/$(TARGET_ASAN_BASENAME)
NDK_APP_UBSAN := $(NDK_APP_DST_DIR)/$(TARGET_UBSAN_BASENAME)

NDK_ALL_LDFLAGS := $(NDK_APP_LDFLAGS)
$(foreach __module,$(__ndk_modules),\
$(eval NDK_ALL_LDFLAGS += $(__ndk_modules.$(__module).LDFLAGS)))
NDK_FSANITIZE_LDFLAGS := $(filter -fsanitize=%,$(NDK_ALL_LDFLAGS))
NDK_SANITIZERS := $(patsubst -fsanitize=%,%,$(NDK_FSANITIZE_LDFLAGS))

NDK_SANITIZER_NAME := UBSAN
NDK_SANITIZER_FSANITIZE_ARGS := undefined
include $(BUILD_SYSTEM)/install_sanitizer.mk

NDK_SANITIZER_NAME := ASAN
NDK_SANITIZER_FSANITIZE_ARGS := address
include $(BUILD_SYSTEM)/install_sanitizer.mk
48 changes: 5 additions & 43 deletions build/core/setup-toolchain.mk
Original file line number Diff line number Diff line change
Expand Up @@ -158,49 +158,7 @@ SYSROOT_ARCH_INC_ARG := \

clean-installed-binaries::

# Ensure that for debuggable applications, gdbserver will be copied to
# the proper location

NDK_APP_GDBSERVER := $(NDK_APP_DST_DIR)/gdbserver
NDK_APP_GDBSETUP := $(NDK_APP_DST_DIR)/gdb.setup

ifeq ($(NDK_APP_DEBUGGABLE),true)
ifeq ($(TARGET_SONAME_EXTENSION),.so)

installed_modules: $(NDK_APP_GDBSERVER)

$(NDK_APP_GDBSERVER): PRIVATE_ABI := $(TARGET_ARCH_ABI)
$(NDK_APP_GDBSERVER): PRIVATE_NAME := $(TOOLCHAIN_NAME)
$(NDK_APP_GDBSERVER): PRIVATE_SRC := $(TARGET_GDBSERVER)
$(NDK_APP_GDBSERVER): PRIVATE_DST := $(NDK_APP_GDBSERVER)

$(call generate-file-dir,$(NDK_APP_GDBSERVER))

$(NDK_APP_GDBSERVER): clean-installed-binaries
$(call host-echo-build-step,$(PRIVATE_ABI),Gdbserver) "[$(PRIVATE_NAME)] $(call pretty-dir,$(PRIVATE_DST))"
$(hide) $(call host-install,$(PRIVATE_SRC),$(PRIVATE_DST))
endif

# Install gdb.setup for both .so and .bc projects
ifneq (,$(filter $(TARGET_SONAME_EXTENSION),.so .bc))
installed_modules: $(NDK_APP_GDBSETUP)

$(NDK_APP_GDBSETUP): PRIVATE_ABI := $(TARGET_ARCH_ABI)
$(NDK_APP_GDBSETUP): PRIVATE_DST := $(NDK_APP_GDBSETUP)
$(NDK_APP_GDBSETUP): PRIVATE_SOLIB_PATH := $(TARGET_OUT)
$(NDK_APP_GDBSETUP): PRIVATE_SRC_DIRS := $(SYSROOT_INC)

$(NDK_APP_GDBSETUP):
$(call host-echo-build-step,$(PRIVATE_ABI),Gdbsetup) "$(call pretty-dir,$(PRIVATE_DST))"
$(hide) $(HOST_ECHO) "set solib-search-path $(call host-path,$(PRIVATE_SOLIB_PATH))" > $(PRIVATE_DST)
$(hide) $(HOST_ECHO) "directory $(call host-path,$(call remove-duplicates,$(PRIVATE_SRC_DIRS)))" >> $(PRIVATE_DST)

$(call generate-file-dir,$(NDK_APP_GDBSETUP))

# This prevents parallel execution to clear gdb.setup after it has been written to
$(NDK_APP_GDBSETUP): clean-installed-binaries
endif
endif
include $(BUILD_SYSTEM)/gdb.mk

# free the dictionary of LOCAL_MODULE definitions
$(call modules-clear)
Expand All @@ -211,6 +169,10 @@ $(call ndk-stl-select,$(NDK_APP_STL))
# module declarations, but does not populate the dependency graph yet.
include $(NDK_APP_BUILD_SCRIPT)

# Comes after NDK_APP_BUILD_SCRIPT because we need to know if *any* module has
# -fsanitize in its ldflags.
include $(BUILD_SYSTEM)/sanitizers.mk

$(call ndk-stl-add-dependencies,$(NDK_APP_STL))

# recompute all dependencies between modules
Expand Down
3 changes: 3 additions & 0 deletions build/core/toolchains/aarch64-linux-android-clang/setup.mk
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ TOOLCHAIN_PREFIX := $(TOOLCHAIN_ROOT)/bin/$(TOOLCHAIN_NAME)-

LLVM_TRIPLE := aarch64-none-linux-android

TARGET_ASAN_BASENAME := libclang_rt.asan-aarch64-android.so
TARGET_UBSAN_BASENAME := libclang_rt.ubsan_standalone-aarch64-android.so

TARGET_CFLAGS := \
-gcc-toolchain $(call host-path,$(TOOLCHAIN_ROOT)) \
-target $(LLVM_TRIPLE) \
Expand Down
3 changes: 3 additions & 0 deletions build/core/toolchains/arm-linux-androideabi-clang/setup.mk
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ TOOLCHAIN_PREFIX := $(TOOLCHAIN_ROOT)/bin/$(TOOLCHAIN_NAME)-
# CFLAGS and LDFLAGS
#

TARGET_ASAN_BASENAME := libclang_rt.asan-arm-android.so
TARGET_UBSAN_BASENAME := libclang_rt.ubsan_standalone-arm-android.so

TARGET_CFLAGS := \
-gcc-toolchain $(call host-path,$(TOOLCHAIN_ROOT)) \
-fpic \
Expand Down
3 changes: 3 additions & 0 deletions build/core/toolchains/x86-clang/setup.mk
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ TOOLCHAIN_PREFIX := $(TOOLCHAIN_ROOT)/bin/$(TOOLCHAIN_NAME)-

LLVM_TRIPLE := i686-none-linux-android

TARGET_ASAN_BASENAME := libclang_rt.asan-i686-android.so
TARGET_UBSAN_BASENAME := libclang_rt.ubsan_standalone-i686-android.so

TARGET_CFLAGS := \
-gcc-toolchain $(call host-path,$(TOOLCHAIN_ROOT)) \
-target $(LLVM_TRIPLE) \
Expand Down
3 changes: 3 additions & 0 deletions build/core/toolchains/x86_64-clang/setup.mk
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ TOOLCHAIN_PREFIX := $(TOOLCHAIN_ROOT)/bin/$(TOOLCHAIN_NAME)-

LLVM_TRIPLE := x86_64-none-linux-android

TARGET_ASAN_BASENAME := libclang_rt.asan-x86_64-android.so
TARGET_UBSAN_BASENAME := libclang_rt.ubsan_standalone-x86_64-android.so

TARGET_CFLAGS := \
-gcc-toolchain $(call host-path,$(TOOLCHAIN_ROOT)) \
-target $(LLVM_TRIPLE) \
Expand Down
59 changes: 0 additions & 59 deletions ndk/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,60 +443,6 @@ def disable_verity_and_wait_for_reboot(device):
device.wait()


def asan_device_setup(ndk_path, device):
toolchain_lib_path = os.path.join(
ndk_path, 'toolchains/llvm/prebuilt', ndk.hosts.get_host_tag(ndk_path),
'lib64/clang')
# The lib64/clang directory will contain both a $MAJOR.$MINOR and a
# $MAJOR.$MINOR.$REVISION directory. They should be identical, so just pick
# one.
# toolchain_lib_path = os.path.join(
# toolchain_lib_path, os.listdir(toolchain_lib_path)[0])
# FIXME: Except at the moment the toolchain install is broken and 5.0 !=
# 5.0.1.
toolchain_lib_path = os.path.join(toolchain_lib_path, '5.0')
path = os.path.join(toolchain_lib_path, 'bin/asan_device_setup')
cmd = [path, '--device', device.serial]
logger().info('%s: asan_device_setup', device.name)
# Use call_output to keep the call quiet unless something goes wrong.
result, out = ndk.ext.subprocess.call_output(cmd)
if result != 0:
# The script sometimes fails on the first try >:(
logger().info(
'%s: asan_device_setup failed once, retrying', device.name)
result, out = ndk.ext.subprocess.call_output(cmd)
if result != 0:
# The script sometimes fails on the first try >:(
result, out = ndk.ext.subprocess.call_output(cmd)
raise RuntimeError('{}: asan_device_setup failed:\n{}'.format(
device, out))


def setup_asan_for_device(worker, ndk_path, device):
worker.status = 'Performing ASAN setup for {}'.format(device)
disable_verity_and_wait_for_reboot(device)
asan_device_setup(ndk_path, device)


def perform_asan_setup(workqueue, ndk_path, groups_for_config):
# asan_device_setup is a shell script, so no asan there.
if os.name == 'nt':
return

devices = []
for groups in groups_for_config.values():
for group in groups:
devices.extend(group.devices)
devices = sorted(list(set(devices)))

for device in devices:
if device.can_use_asan():
workqueue.add_task(setup_asan_for_device, ndk_path, device)

finish_workqueue_with_ui(workqueue)
print('Finished ASAN setup')


def run_test(worker, test):
device = worker.data[0]
worker.status = 'Running {}'.format(test.name)
Expand Down Expand Up @@ -901,11 +847,6 @@ def run_tests(args):
push_tests_to_devices(
workqueue, test_dist_dir, groups_for_config, can_use_sync)
results.add_timing_report('Push', push_timer)

asan_setup_timer = ndk.timer.Timer()
with asan_setup_timer:
perform_asan_setup(workqueue, args.ndk, groups_for_config)
results.add_timing_report('ASAN setup', asan_setup_timer)
finally:
workqueue.terminate()
workqueue.join()
Expand Down
32 changes: 0 additions & 32 deletions ndk/test/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,38 +138,6 @@ def can_run_build_config(self, config):

return True

def can_use_asan(self):
# ASAN is currently only supported for 32-bit ARM and x86...
asan_abis = {
'armeabi',
'armeabi-v7a',
'x86'
}

if not asan_abis.intersection(set(self.abis)):
logger().info('Cannot use ASAN: no ASAN supported ABIs (%s)',
', '.join(sorted(list(asan_abis))))
return False

# On KitKat and newer...
if self.version < 19:
logger().info('Cannot use ASAN: device is too old '
'(is android-%s, minimum android-19)', self.version)
return False

# On rooted devices.
if not self.is_debuggable:
logger().info('Cannot use ASAN: device must be rooted')
return False

# Fugu's system image doesn't have enough space left for even the ASAN
# library.
if self.name == 'fugu':
logger().info('Cannot use ASAN: system partition full')
return False

return True

@property
def supports_pie(self):
return self.version >= 16
Expand Down

0 comments on commit bd4090b

Please sign in to comment.