Index: asan_device_setup.py =================================================================== --- asan_device_setup.py +++ asan_device_setup.py @@ -0,0 +1,397 @@ +#! /usr/bin/env monkeyrunner +# ===- 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. +# +# ===------------------------------------------------------------------------===# +import os +import sys +import tempfile +import shutil +import subprocess + +class asanDeviceSetup: + def __init__(self): + self.revert = False + self.extra_options = "" + self.deviceId = "" + self.lib = "" + self.use_su = False + + def adbShellCmd(self, cmd): + cmd = "adb shell " + cmd + output,error = subprocess.Popen( + cmd, universal_newlines=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + if(error != ""): + print( "command: [" + cmd + "] output: [" + output + "] error: [" + error +"]" ) + return output + + def adbPullCmd(self, cmd): + cmd = "adb pull " + cmd + output, error = subprocess.Popen( + cmd, universal_newlines=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + if(error != ""): + print( "command: [" + cmd + "] output: [" + output + "] error: [" + error +"]" ) + return output + + def adbPushCmd(self, cmd): + cmd = "adb push " + cmd + output, error = subprocess.Popen( + cmd, universal_newlines=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + if(error != ""): + print( "command: [" + cmd + "] output: [" + output + "] error: [" + error +"]" ) + return output + + def adbCmd(self, cmd): + cmd = "adb " + cmd + output, error = subprocess.Popen( + cmd, universal_newlines=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + if(error != ""): + print( "command: [" + cmd + "] output: [" + output + "] error: [" + error +"]" ) + return output + + def runAsan(self): + self.parseArgsLoop() + if self.use_su: + canSU = self.adbShellCmd('su -c echo foo') + if not ("foo" in canSU): + print "ERROR: Cannot use 'su -c':" + print "$ adb shell su -c \"echo foo\"" + print "Check that 'su' binary is correctly installed on the device or omit" + print " --use-su flag" + sys.exit() + + print ">> Remounting /system rw" + if not self.use_su: + self.WaitForConnection() + self.adbCmd("root") + self.WaitForConnection() + # Make SE Linux happy by keeping app_process wrapper and the shell it runs on in zygote domain. + ENFORCING = self.RunShell("getenforce")=="Enforcing" + if ENFORCING: + self.RunShell("setenforce 0") + + self.Remount() + self.WaitForConnection() + self.getArch() + + print "Target achitecture: " + self.Arch + + self.ASAN_RT = "libclang_rt.asan-" + self.Arch + "-android.so" + if self.Arch64 != "": + print "Target architecture: " + self.Arch64 + self.ASAN_RT64="libclang_rt.asan-" + self.Arch64 + "-android.so" + else: + self.ASAN_RT64="" + + self.PRE_L= self.adbShellCmd("getprop ro.build.version.release")[0] <= 4 + + if(self.revert): + self.RunRevert() + sys.exit() + + if(self.lib != ""): + if os.path.isdir(self.lib): + self.ASAN_RT_PATH=self.lib + elif os.path.isfile(self.lib): + self.ASAN_RT_PATH=os.path.dirname(os.path.normpath(self.lib)) + else: + # 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. + HERE=os.path.dirname(os.path.realpath(__file__)) + self.ASAN_RT_PATH="" + if os.path.isfile(os.path.join(HERE, "..", "lib", self.ASAN_RT)): + self.ASAN_RT_PATH = os.path.normpath(os.path.join(HERE, "..", "lib")) + if os.path.isfile(os.path.join(HERE, "..", "lib", "asan", "self.ASAN_RT")): + self.ASAN_RT_PATH = os.path.normpath(os.path.join(HERE, "..", "lib", "asan")) + if os.path.isfile(os.path.join(HERE, "..", "lib", "linux", self.ASAN_RT)): + self.ASAN_RT_PATH = os.path.normpath(os.path.join(HERE, "..", "lib")) + for subdir, dirs, files in os.walk(os.path.join(HERE, "..", "lib", "clang")): + if os.path.isfile(os.path.join(HERE, "..", "lib", "clang", subdir, self.ASAN_RT)): + self.ASAN_RT_PATH = os.path.normpath(os.path.join(HERE, ".." "lib", "clang", subdir)) + for subdir, dirs, files in os.walk(os.path.join(HERE, "..", "lib64", "clang")): + if os.path.isfile(os.path.join(HERE, "..", "lib64", "clang", subdir, self.ASAN_RT)): + self.ASAN_RT_PATH = os.path.normpath(os.path.join(HERE, "..", "lib64", "clang", subdir)) + if not os.path.isfile(self.ASAN_RT_PATH + "/" + self.ASAN_RT) and not os.path.isfile(self.ASAN_RT_PATH + "/" + self.ASAN_RT64): + print ">> Asan runtime library not found" + sys.exit() + + TMPDIR = tempfile.mkdtemp() + + if(self.PRE_L): + if not "error:" in self.pull("/system/bin/app_process.real", None): + print ">> Old-style ASan installation detected. Reverting." + self.RunShell("mv /system/bin/app_process.real /system/bin/app_process") + print ">> Pre-L device detected. Setting up app_process symlink." + self.RunShell("mv /system/bin/app_process /system/bin/app_process32") + self.RunShell("ln -s /system/bin/app_process32 /system/bin/app_process") + + print ">> Copying files from the device" + + if not self.ASAN_RT64 == "": + self.pull("/system/lib/" + self.ASAN_RT, TMPDIR) + self.pull("/system/lib64/" + self.ASAN_RT64, TMPDIR) + self.pull("/system/bin/app_process32", TMPDIR) + self.pull("/system/bin/app_process32.real", TMPDIR) + self.pull("/system/bin/app_process64", TMPDIR) + self.pull("/system/bin/asanwrapper", TMPDIR) + self.pull("/system/bin/asanwrapper64", TMPDIR) + else: + self.pull("/system/lib/" + self.ASAN_RT, TMPDIR) + self.pull("/system/bin/app_process32", TMPDIR) + self.pull("/system/bin/app_process32.real", TMPDIR) + self.pull("/system/bin/asanwrapper", TMPDIR) + + prevInstall = False + if os.path.isfile(TMPDIR + "/app_precess.wrap") or os.path.isfile(TMPDIR + "/app_process64.real"): + prevInstall = True + + if prevInstall: + print ">> Previous installation detected" + else: + print ">> New installation" + + print ">> Generating wrappers" + shutil.copy2(self.ASAN_RT_PATH + "/" + self.ASAN_RT, TMPDIR) + if not self.ASAN_RT64 == "": + shutil.copy2(self.ASAN_RT_PATH + "/" + self.ASAN_RT64, TMPDIR) + + self.ASAN_OPTIONS="start_deactivated=1,malloc_context_size=0" + # On Android-L not allowing user segv handler breaks some applications. + if not self.PRE_L: + self.ASAN_OPTIONS +=",allow_user_segv_handler=1" + + if not self.extra_options == "": + self.ASAN_OPTIONS += "," + self.extra_options + + # Zygote wrapper. + if os.path.isfile(TMPDIR + "/app_process64"): + # A 64-bit device. + if not os.path.isfile(TMPDIR + "/app_process64.real"): + # New Installation. + shutil.move(TMPDIR + "/app_process32", TMPDIR + "/app_process32.real") + shutil.move(TMPDIR + "/app_process64", TMPDIR + "/app_process64.real") + self.generateZygotWrapper(TMPDIR + "/app_process32", "/system/bin/app_process32.real", self.ASAN_RT) + self.generateZygotWrapper(TMPDIR + "/app_process64", "/system/bin/app_process64.real", self.ASAN_RT64) + else: + self.generateZygotWrapper(TMPDIR + "/app_process32", "/system/bin/app_process32.real", self.ASAN_RT) + + # General command-line tool wrapper (use for anything that's not started as + # zygote). + outFile = open(TMPDIR + "/asanwrapper", "w") + outFile.truncate() + outFile.write("#!/system/bin/sh\n") + outFile.write("LD_PRELOAD=" + self.ASAN_RT + "\n") + outFile.write("exec $@\n") + outFile.close() + + if not self.ASAN_RT64 == "": + outFile = open(TMPDIR + "/asanwrapper64", "w") + outFile.truncate() + outFile.write("#!/system/bin/sh\n") + outFile.write("LD_PRELOAD=" + self.ASAN_RT64 + "\n") + outFile.write("exec $@\n") + outFile.close() + + if self.PRE_L: + CTX = "u:object_r:system_file:s0" + else: + CTX = "u:object_r:zygote_exec:s0" + + print ">> Pushing files to the device." + + if not self.ASAN_RT64 == "": + self.install(TMPDIR + "/" + self.ASAN_RT, "/system/lib", "644") + self.install(TMPDIR + "/" + self.ASAN_RT64, "/system/lib64", "644") + self.install(TMPDIR + "/app_process32", "/system/bin", "755", CTX) + self.install(TMPDIR + "/app_process32.real", "/system/bin", "755", CTX) + self.install(TMPDIR + "/app_process64", "/system/bin", "755", CTX) + self.install(TMPDIR + "/app_process64.real", "/system/bin", "755", CTX) + self.install(TMPDIR + "/asanwrapper", "/system/bin", "755") + self.install(TMPDIR + "/asanwrapper64", "/system/bin", "755") + else: + self.install(TMPDIR + "/" + self.ASAN_RT, "/system/lib", "644") + self.install(TMPDIR + "/app_process32", "/system/bin", "755", CTX) + self.install(TMPDIR + "/app_process32.wrap", "/system/bin", "755", CTX) + self.install(TMPDIR + "/asanwrapper", "/system/bin", "755") + + self.RunShell("rm /system/bin/app_process") + self.RunShell("ln -s /system/bin/app_process.wrap /system/bin/app_process") + + self.RunShell("cp /system/bin/sh /system/bin/sh-from-zygote") + self.RunShell("chcon " + CTX + " /system/bin/sh-from-zygote") + if ENFORCING: + self.RunShell("setenforce 1") + + print ">> Restarting shell(asynchronous)" + self.RunShell("stop") + self.RunShell("start") + print ">> please wait until the device restarts" + + def RunShell(self, cmd): + if self.use_su: + return self.adbShellCmd("su -c \"" + cmd + "\"") + return self.adbShellCmd(cmd) + + def WaitForConnection(self): + self.adbCmd("wait-for-device") + + def usage(self): + print "usage: " + sys.argv[0] + "[--revert] [--device device-id] [--lib path] [--extra-options options]" + print "\t--revert: Uninstall ASan from the device." + print "\t--lib: Path to Asan runtime library." + print "\t--extra-options: Extra ASAN_OPTIONS" + print "\t--device: Install to the given device. Use 'adb devices' to find" + print "\t device-id." + print "\t--use-su: Use 'su -c' prefix for every adb command instead of using" + print "\t 'adb root' once." + + def parseArgsLoop(self): + argList = sys.argv[1:] + for arg in argList: + if(arg == "--revert"): + self.revert = True + continue + if(arg == "--extra-options"): + self.extra_options = next(argList, None) + if self.extra_options is None: + print "--extra-options requires an argument." + sys.exit() + continue + if(arg == "--lib"): + self.lib = next(argList, None) + if self.lib is None: + print "--lib requires an argument." + sys.exit() + continue + if(arg == "--device"): + self.device = next(argList, None) + if self.deviceId is None: + print "--device requires an argument." + sys.exit() + continue + if(arg == "--use-su"): + self.use_su = True + continue + self.usage() + sys.exit() + + def Remount(self): + if not self.use_su: + self.adbCmd("remount") + else: + storage = self.adbShellCmd("mount \"2>&1\"") + for mntPoint in storage.splitlines(): + if " /system " in mntPoint: + location = mntPoint.split()[0] + print "Remounting " + location + " at /system" + self.RunShell("mount -o rw,remount /system") + + def getArch(self): + abiType = self.adbShellCmd("getprop ro.product.cpu.abi") + self.Arch64 = "" + if "x86" in abiType: + self.Arch = "i686" + if "armeabi" in abiType: + self.Arch = "arm" + + if "arm64-v8a" in abiType: + self.Arch = "arm" + self.Arch64 = "aarch64" + + if self.Arch == "": + print "Unrecognized device ABI: " + abiType + sys.exit() + + def RunRevert(self): + print "Uninstalling ASan" + + foundAppProcess64 = self.RunShell("ls -l /system/bin/app_process64.real") + if(self.PRE_L): + print ">>Pre-L device detected." + self.RunShell("mv /system/bin/app_process.real /system/bin/app_process") + self.RunShell("rm /system/bin/asanwrapper") + elif(foundAppProcess64 != ""): + print ">>64-bit device detected." + self.RunShell("mv /system/bin/app_process32.real /system/bin/app_process32") + self.RunShell("mv /system/bin/app_process64.real /system/bin/app_process64") + self.RunShell("rm /system/bin/asanwrapper") + self.RunShell("rm /system/bin/asanwrapper64") + else: + print ">>32-bit device detected." + self.RunShell("rm /system/bin/app_process.wrap") + self.RunShell("rm /system/bin/asanwrapper") + self.RunShell("rm /system/bin/app_process") + self.RunShell("ln -s /system/bin/app_process32 /system/bin/app_process") + print ">> Restarting Shell" + self.RunShell("stop") + self.RunShell("start") + + # Remove the library on the last step to give a chance to the 'su' binary to + # be executed without a problem. + self.RunShell("rm /system/lib/" + self.ASAN_RT) + print ">> Done" + + def push(self, item, dest): + if not self.use_su: + self.adbPushCmd(item + " " + dest) + else: + FILENAME = os.path.basename(item) + self.adbPushCmd(item + " /data/local/tmp/" + FILENAME) + self.RunShell("[ -f " + dest + "/" + FILENAME + " ] && su -c rm " + dest) + self.RunShell("[ -f /data/local/tmp/" + FILENAME + " ] && su -c cat /data/local/tmp/" + FILENAME + " > " + dest) + self.RunShell("[ -f /data/local/tmp/" + FILENAME + " ] && su -c rm /data/local/tmp/" + FILENAME) + + def pull(self, item, dest): + if not self.use_su: + return self.adbPullCmd(item + " " + dest) + else: + FILENAME = os.path.basename(item) + self.RunShell("[ -f /data/localtmp/" + FILENAME + " ] && su -c rm /data/localtmp/" + FILENAME) + self.RunShell("[ -f " + item + " ] && su -c cat " + item + " > /data/local/tmp/" + FILENAME) + self.RunShell("[ -f /data/local/tmp/" + FILENAME + " ] && su -c chown root.shell /data/local/tmp/" + FILENAME) + self.RunShell("[ -f /data/local/tmp/" + FILENAME + " ] && su -c chmod 755 /data/local/tmp/" + FILENAME) + response = self.adbPullCmd("/data/local/tmp/" + FILENAME + " " + dest ) + self.RunShell("[ -f /data/local/tmp/" + FILENAME + " ] && su -c rm \"/data/local/tmp/" + FILENAME + "\"") + return response + + def generateZygotWrapper(self, src, dest, asan_rt): + if not self.PRE_L: + # LD_PRELOAD parsing is broken in N if it starts with ":". Luckily, it is + # unset in the system environment since L. + ld_preload=asan_rt + else: + ld_preload="\%LD_PRELOAD\%:"+asan_rt + + outFile = open(src, "w") + outFile.truncate() + outFile.write("#!/system/bin/sh-from-zygote\n") + outFile.write("ASAN_OPTIONS=" + self.ASAN_OPTIONS + "\n") + outFile.write("ASAN_ACTIVATION_OPTIONS=include_if_exists=/data/local/tmp/asan.options.%b\n") + outFile.write("LD_PRELOAD=" + ld_preload + "\n") + outFile.write("exec " + dest + " $@\n") + outFile.close() + + def install(self, src, dest, mode="", context=""): + base = os.path.basename(src) + print "Installing " + dest + "/" + base + " " + mode + " " + context + self.push(src, dest + "/" + base) + self.RunShell("chown root.shell " + dest + "/" + base) + if not mode=="": + self.RunShell("chmod " + mode + " " + dest + "/" + base) + if not context == "": + self.RunShell("chcon " + context + " " + dest + "/" + base) + + +asn = asanDeviceSetup() +asn.runAsan() \ No newline at end of file