| #!/bin/bash |
| #===- lib/asan/scripts/asan_device_setup -----------------------------------===# |
| # |
| # The LLVM Compiler Infrastructure |
| # |
| # This file is distributed under the University of Illinois Open Source |
| # License. See LICENSE.TXT for details. |
| # |
| # 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 remount,rw $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,malloc_context_size=0 |
| |
| function generate_zygote_wrapper { # from, to, asan_rt |
| local _from=$1 |
| local _to=$2 |
| local _asan_rt=$3 |
| 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 |
| else |
| local _ld_preload=\$LD_PRELOAD:$_asan_rt |
| 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. |
| 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" "$ASAN_RT" |
| generate_zygote_wrapper "app_process64" "/system/bin/app_process64.real" "$ASAN_RT64" |
| else |
| # A 32-bit device. |
| generate_zygote_wrapper "app_process.wrap" "/system/bin/app_process32" "$ASAN_RT" |
| 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 \\ |
| exec \$@ |
| |
| EOF |
| |
| if [[ -n "$ASAN_RT64" ]]; then |
| cat <<EOF >"$TMPDIR/asanwrapper64" |
| #!/system/bin/sh |
| LD_PRELOAD=$ASAN_RT64 \\ |
| 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 |
| 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 /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, due to b/38114603, 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" |