diff --git a/CHANGELOG.md b/CHANGELOG.md index a5f0c12e..ef1ba4f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,9 +58,20 @@ NDK of a local development build. * `ANDROID_NDK_BETA`: The beta version of the NDK. This is 0 for a stable release. + * Added support for `APP_WRAP_SH` to ndk-build. + * This variable points to a shell script (relative to your Android.mk) that + will be installed as a [wrap.sh] file in your APK. + * Available in both an ABI-generic form (`APP_WRAP_SH`), which will install + a single script for every ABI, and an ABI-specific form + (`APP_WRAP_SH_arm64-v8a`, etc) to allow for per-ABI customization of the + wrap.sh script. * 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]. + * When using ASAN, ndk-build will install a wrap.sh file to set up ASAN for + your app if you have not specified your own wrap.sh. If you have specified + your own wrap.sh, you can add ASAN support to it as described + [here](https://github.com/google/sanitizers/wiki/AddressSanitizerOnAndroidO). [wrap.sh]: https://developer.android.com/ndk/guides/wrap-script.html [Issue 540]: https://github.com/android-ndk/ndk/issues/540 diff --git a/build/core/add-application.mk b/build/core/add-application.mk index 3b34ca44..5341e5a5 100644 --- a/build/core/add-application.mk +++ b/build/core/add-application.mk @@ -198,6 +198,39 @@ ifneq ($(filter $(APP_STL),gnustl_static gnustl_shared stlport_static stlport_sh information.) endif +# wrap.sh files can be specified in the user's Application.mk in either an +# ABI-generic (APP_WRAP_SH) or ABI-specific (APP_WRAP_SH_x86, etc) fashion. +# These two approaches cannot be combined; if any ABI-specific wrap.sh files are +# specified then it is an error to also specify an ABI-generic one. +# +# After this block, only the ABI-specific values should be checked; if there is +# an ABI-generic script specified the ABI-specific variables will be populated +# with the generic script. +NDK_NO_USER_WRAP_SH := true +ifneq ($(APP_WRAP_SH),) + NDK_NO_USER_WRAP_SH := false +endif + +NDK_HAVE_ABI_SPECIFIC_WRAP_SH := false +$(foreach _abi,$(NDK_ALL_ABIS),\ + $(if $(APP_WRAP_SH_$(_abi)),\ + $(eval NDK_HAVE_ABI_SPECIFIC_WRAP_SH := true))) + +ifeq ($(NDK_HAVE_ABI_SPECIFIC_WRAP_SH),true) + # It is an error to have both ABI-specific and ABI-generic wrap.sh files + # specified. + ifneq ($(APP_WRAP_SH),) + $(call __ndk_error,Found both ABI-specific and ABI-generic APP_WRAP_SH \ + directives. Must use either all ABI-specific or only ABI-generic.) + endif + NDK_NO_USER_WRAP_SH := false +else + # If we have no ABI-specific wrap.sh files but we *do* have an ABI-generic + # one, install the generic one for all ABIs. + $(foreach _abi,$(NDK_ALL_ABIS),\ + $(eval APP_WRAP_SH_$(_abi) := $(APP_WRAP_SH))) +endif + $(if $(call get,$(_map),defined),\ $(call __ndk_info,Weird, the application $(_app) is already defined by $(call get,$(_map),defined))\ $(call __ndk_error,Aborting)\ diff --git a/build/core/definitions.mk b/build/core/definitions.mk index 5e3a6529..99f2fa4e 100644 --- a/build/core/definitions.mk +++ b/build/core/definitions.mk @@ -1339,6 +1339,14 @@ NDK_APP_VARS_OPTIONAL := \ APP_SHORT_COMMANDS \ APP_STL \ APP_THIN_ARCHIVE \ + APP_WRAP_SH \ + +# NDK_ALL_ABIS is not configured yet. +NDK_APP_VARS_OPTIONAL += \ + APP_WRAP_SH_armeabi-v7a \ + APP_WRAP_SH_arm64-v8a \ + APP_WRAP_SH_x86 \ + APP_WRAP_SH_x86_64 \ # the list of all variables that may appear in an Application.mk file # or defined by the build scripts. diff --git a/build/core/install_wrap_sh.mk b/build/core/install_wrap_sh.mk new file mode 100644 index 00000000..b5701900 --- /dev/null +++ b/build/core/install_wrap_sh.mk @@ -0,0 +1,29 @@ +# +# 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 wrap.sh files to the app's out directory. + +NDK_WRAP_SH := $(NDK_APP_DST_DIR)/wrap.sh +$(call generate-file-dir,$(NDK_WRAP_SH)) + +installed_modules: $(NDK_WRAP_SH) + +WRAP_SH_SRC := $(call local-source-file-path,$(NDK_APP_WRAP_SH_$(TARGET_ARCH_ABI))) + +$(NDK_WRAP_SH): PRIVATE_ABI := $(TARGET_ARCH_ABI) +$(NDK_WRAP_SH): $(WRAP_SH_SRC) clean-installed-binaries + $(call host-echo-build-step,$(PRIVATE_ABI),wrap.sh "$(call pretty-dir,$@)") + $(hide) $(call host-install,$<,$@) diff --git a/build/core/sanitizers.mk b/build/core/sanitizers.mk index 0dac8e5c..76d5a0d7 100644 --- a/build/core/sanitizers.mk +++ b/build/core/sanitizers.mk @@ -38,3 +38,12 @@ include $(BUILD_SYSTEM)/install_sanitizer.mk NDK_SANITIZER_NAME := ASAN NDK_SANITIZER_FSANITIZE_ARGS := address include $(BUILD_SYSTEM)/install_sanitizer.mk + +# If the user has not specified their own wrap.sh and is using ASAN, install a +# default ASAN wrap.sh for them. +ifneq (,$(filter address,$(NDK_SANITIZERS))) + ifeq ($(NDK_NO_USER_WRAP_SH),true) + NDK_APP_WRAP_SH_$(TARGET_ARCH_ABI) := \ + $(NDK_ROOT)/wrap.sh/asan.$(TARGET_ARCH_ABI).sh + endif +endif diff --git a/build/core/setup-toolchain.mk b/build/core/setup-toolchain.mk index 8a8bc890..2fe6f612 100644 --- a/build/core/setup-toolchain.mk +++ b/build/core/setup-toolchain.mk @@ -174,6 +174,10 @@ include $(NDK_APP_BUILD_SCRIPT) # -fsanitize in its ldflags. include $(BUILD_SYSTEM)/sanitizers.mk +ifneq ($(NDK_APP_WRAP_SH_$(TARGET_ARCH_ABI)),) + include $(BUILD_SYSTEM)/install_wrap_sh.mk +endif + $(call ndk-stl-add-dependencies,$(NDK_APP_STL)) # recompute all dependencies between modules diff --git a/checkbuild.py b/checkbuild.py index 5bd9a9a0..11f7c0e4 100755 --- a/checkbuild.py +++ b/checkbuild.py @@ -1487,6 +1487,16 @@ def validate_notice(self, _install_base): pass +class WrapSh(ndk.builds.PackageModule): + name = 'wrap.sh' + path = 'wrap.sh' + src = build_support.ndk_path('wrap.sh') + + def validate_notice(self, _install_base): + # No license needed for meta. + pass + + class SourceProperties(ndk.builds.Module): name = 'source.properties' path = 'source.properties' @@ -1606,6 +1616,7 @@ def get_modules_to_build(module_names, arches): Sysroot(), SystemStl(), Vulkan(), + WrapSh(), ] diff --git a/tests/build/wrap_sh/__init__.py b/tests/build/wrap_sh/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/build/wrap_sh/project/jni/Android.mk b/tests/build/wrap_sh/project/jni/Android.mk new file mode 100644 index 00000000..365bbad8 --- /dev/null +++ b/tests/build/wrap_sh/project/jni/Android.mk @@ -0,0 +1,6 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := foo +LOCAL_SRC_FILES := foo.cpp +include $(BUILD_SHARED_LIBRARY) diff --git a/tests/build/wrap_sh/project/jni/Application.mk b/tests/build/wrap_sh/project/jni/Application.mk new file mode 100644 index 00000000..5622b108 --- /dev/null +++ b/tests/build/wrap_sh/project/jni/Application.mk @@ -0,0 +1,4 @@ +APP_WRAP_SH_armeabi-v7a := armeabi-v7a.sh +APP_WRAP_SH_arm64-v8a := arm64-v8a.sh +APP_WRAP_SH_x86:= x86.sh +APP_WRAP_SH_x86_64:= x86_64.sh diff --git a/tests/build/wrap_sh/project/jni/arm64-v8a.sh b/tests/build/wrap_sh/project/jni/arm64-v8a.sh new file mode 100644 index 00000000..e4a17414 --- /dev/null +++ b/tests/build/wrap_sh/project/jni/arm64-v8a.sh @@ -0,0 +1 @@ +arm64-v8a diff --git a/tests/build/wrap_sh/project/jni/armeabi-v7a.sh b/tests/build/wrap_sh/project/jni/armeabi-v7a.sh new file mode 100644 index 00000000..e4e4e6c1 --- /dev/null +++ b/tests/build/wrap_sh/project/jni/armeabi-v7a.sh @@ -0,0 +1 @@ +armeabi-v7a diff --git a/tests/build/wrap_sh/project/jni/foo.cpp b/tests/build/wrap_sh/project/jni/foo.cpp new file mode 100644 index 00000000..85e6cd8c --- /dev/null +++ b/tests/build/wrap_sh/project/jni/foo.cpp @@ -0,0 +1 @@ +void foo() {} diff --git a/tests/build/wrap_sh/project/jni/x86.sh b/tests/build/wrap_sh/project/jni/x86.sh new file mode 100644 index 00000000..7306afab --- /dev/null +++ b/tests/build/wrap_sh/project/jni/x86.sh @@ -0,0 +1 @@ +x86 diff --git a/tests/build/wrap_sh/project/jni/x86_64.sh b/tests/build/wrap_sh/project/jni/x86_64.sh new file mode 100644 index 00000000..1c093466 --- /dev/null +++ b/tests/build/wrap_sh/project/jni/x86_64.sh @@ -0,0 +1 @@ +x86_64 diff --git a/tests/build/wrap_sh/test.py b/tests/build/wrap_sh/test.py new file mode 100644 index 00000000..64f3cd31 --- /dev/null +++ b/tests/build/wrap_sh/test.py @@ -0,0 +1,54 @@ +# +# 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. +# +"""Check for correct link order from ndk-build. +""" +import os +import subprocess +import sys +import textwrap + + +def run_test(ndk_path, abi, platform, toolchain, build_flags): + """Checks that the proper wrap.sh scripts were installed.""" + ndk_build = os.path.join(ndk_path, 'ndk-build') + if sys.platform == 'win32': + ndk_build += '.cmd' + project_path = 'project' + ndk_args = build_flags + [ + 'APP_ABI=' + abi, + 'APP_PLATFORM=android-{}'.format(platform), + 'NDK_TOOLCHAIN_VERSION=' + toolchain, + ] + proc = subprocess.Popen([ndk_build, '-C', project_path] + ndk_args, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + out, _ = proc.communicate() + out = out.decode('utf-8') + if proc.returncode != 0: + return proc.returncode == 0, out + + wrap_sh = os.path.join(project_path, 'libs', abi, 'wrap.sh') + if not os.path.exists(wrap_sh): + return False, '{} does not exist'.format(wrap_sh) + + with open(wrap_sh) as wrap_sh_file: + contents = wrap_sh_file.read().strip() + if contents != abi: + return False, textwrap.dedent("""\ + wrap.sh file had wrong contents: + Expected: {} + Actual: {}""".format(abi, contents)) + + return True, '' diff --git a/tests/build/wrap_sh_generic/__init__.py b/tests/build/wrap_sh_generic/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/build/wrap_sh_generic/project/jni/Android.mk b/tests/build/wrap_sh_generic/project/jni/Android.mk new file mode 100644 index 00000000..365bbad8 --- /dev/null +++ b/tests/build/wrap_sh_generic/project/jni/Android.mk @@ -0,0 +1,6 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := foo +LOCAL_SRC_FILES := foo.cpp +include $(BUILD_SHARED_LIBRARY) diff --git a/tests/build/wrap_sh_generic/project/jni/Application.mk b/tests/build/wrap_sh_generic/project/jni/Application.mk new file mode 100644 index 00000000..58296c86 --- /dev/null +++ b/tests/build/wrap_sh_generic/project/jni/Application.mk @@ -0,0 +1 @@ +APP_WRAP_SH := wrap.sh diff --git a/tests/build/wrap_sh_generic/project/jni/foo.cpp b/tests/build/wrap_sh_generic/project/jni/foo.cpp new file mode 100644 index 00000000..85e6cd8c --- /dev/null +++ b/tests/build/wrap_sh_generic/project/jni/foo.cpp @@ -0,0 +1 @@ +void foo() {} diff --git a/tests/build/wrap_sh_generic/project/jni/wrap.sh b/tests/build/wrap_sh_generic/project/jni/wrap.sh new file mode 100644 index 00000000..6ab38927 --- /dev/null +++ b/tests/build/wrap_sh_generic/project/jni/wrap.sh @@ -0,0 +1 @@ +generic diff --git a/tests/build/wrap_sh_generic/test.py b/tests/build/wrap_sh_generic/test.py new file mode 100644 index 00000000..34e6c178 --- /dev/null +++ b/tests/build/wrap_sh_generic/test.py @@ -0,0 +1,54 @@ +# +# 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. +# +"""Check for correct link order from ndk-build. +""" +import os +import subprocess +import sys +import textwrap + + +def run_test(ndk_path, abi, platform, toolchain, build_flags): + """Checks that the proper wrap.sh scripts were installed.""" + ndk_build = os.path.join(ndk_path, 'ndk-build') + if sys.platform == 'win32': + ndk_build += '.cmd' + project_path = 'project' + ndk_args = build_flags + [ + 'APP_ABI=' + abi, + 'APP_PLATFORM=android-{}'.format(platform), + 'NDK_TOOLCHAIN_VERSION=' + toolchain, + ] + proc = subprocess.Popen([ndk_build, '-C', project_path] + ndk_args, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + out, _ = proc.communicate() + out = out.decode('utf-8') + if proc.returncode != 0: + return proc.returncode == 0, out + + wrap_sh = os.path.join(project_path, 'libs', abi, 'wrap.sh') + if not os.path.exists(wrap_sh): + return False, '{} does not exist'.format(wrap_sh) + + with open(wrap_sh) as wrap_sh_file: + contents = wrap_sh_file.read().strip() + if contents != 'generic': + return False, textwrap.dedent("""\ + wrap.sh file had wrong contents: + Expected: generic + Actual: {}""".format(abi, contents)) + + return True, '' diff --git a/tests/build/wrap_sh_none/__init__.py b/tests/build/wrap_sh_none/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/build/wrap_sh_none/project/jni/Android.mk b/tests/build/wrap_sh_none/project/jni/Android.mk new file mode 100644 index 00000000..365bbad8 --- /dev/null +++ b/tests/build/wrap_sh_none/project/jni/Android.mk @@ -0,0 +1,6 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := foo +LOCAL_SRC_FILES := foo.cpp +include $(BUILD_SHARED_LIBRARY) diff --git a/tests/build/wrap_sh_none/project/jni/foo.cpp b/tests/build/wrap_sh_none/project/jni/foo.cpp new file mode 100644 index 00000000..85e6cd8c --- /dev/null +++ b/tests/build/wrap_sh_none/project/jni/foo.cpp @@ -0,0 +1 @@ +void foo() {} diff --git a/tests/build/wrap_sh_none/test.py b/tests/build/wrap_sh_none/test.py new file mode 100644 index 00000000..c7b70fef --- /dev/null +++ b/tests/build/wrap_sh_none/test.py @@ -0,0 +1,44 @@ +# +# 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. +# +"""Check for correct link order from ndk-build. +""" +import os +import subprocess +import sys + + +def run_test(ndk_path, abi, platform, toolchain, build_flags): + """Checks that the proper wrap.sh scripts were installed.""" + ndk_build = os.path.join(ndk_path, 'ndk-build') + if sys.platform == 'win32': + ndk_build += '.cmd' + project_path = 'project' + ndk_args = build_flags + [ + 'APP_ABI=' + abi, + 'APP_PLATFORM=android-{}'.format(platform), + 'NDK_TOOLCHAIN_VERSION=' + toolchain, + ] + proc = subprocess.Popen([ndk_build, '-C', project_path] + ndk_args, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + out, _ = proc.communicate() + out = out.decode('utf-8') + if proc.returncode != 0: + return proc.returncode == 0, out + + wrap_sh = os.path.join(project_path, 'libs', abi, 'wrap.sh') + if os.path.exists(wrap_sh): + return False, '{} should not exist'.format(wrap_sh) + return True, '' diff --git a/wrap.sh/asan.arm64-v8a.sh b/wrap.sh/asan.arm64-v8a.sh new file mode 100644 index 00000000..eb22766d --- /dev/null +++ b/wrap.sh/asan.arm64-v8a.sh @@ -0,0 +1,5 @@ +#!/system/bin/sh +HERE="$(cd "$(dirname "$0")" && pwd)" +export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1 +export LD_PRELOAD=$HERE/libclang_rt.asan-aarch64-android.so +"$@" diff --git a/wrap.sh/asan.armeabi-v7a.sh b/wrap.sh/asan.armeabi-v7a.sh new file mode 100644 index 00000000..e29f45cf --- /dev/null +++ b/wrap.sh/asan.armeabi-v7a.sh @@ -0,0 +1,5 @@ +#!/system/bin/sh +HERE="$(cd "$(dirname "$0")" && pwd)" +export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1 +export LD_PRELOAD=$HERE/libclang_rt.asan-arm-android.so +"$@" diff --git a/wrap.sh/asan.x86.sh b/wrap.sh/asan.x86.sh new file mode 100644 index 00000000..2773dd32 --- /dev/null +++ b/wrap.sh/asan.x86.sh @@ -0,0 +1,5 @@ +#!/system/bin/sh +HERE="$(cd "$(dirname "$0")" && pwd)" +export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1 +export LD_PRELOAD=$HERE/libclang_rt.asan-i686-android.so +"$@" diff --git a/wrap.sh/asan.x86_64.sh b/wrap.sh/asan.x86_64.sh new file mode 100644 index 00000000..c88debac --- /dev/null +++ b/wrap.sh/asan.x86_64.sh @@ -0,0 +1,5 @@ +#!/system/bin/sh +HERE="$(cd "$(dirname "$0")" && pwd)" +export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1 +export LD_PRELOAD=$HERE/libclang_rt.asan-x86_64-android.so +"$@"