| #!/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=i386 | |
| 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" |