Address Sanitizer(Asan) & addr2line

I can use Asan to detected:

  • Stack and heap buffer overflow/underflow

  • Heap use after free

  • Stack use outside scope

  • Double free/wild free

But detecting memory leak is still not support on Android.

AddressSanitizer @Android Source

Leak sanitizer support for Android on arm64#379 @GitHub

Config Address Sanitizer

First add android:debuggable to the application manifest.

Note: This should not enable in release build.

  • local.properties
sdk.dir=/opt/Android/sdk
# ndk.dir=/opt/Android/sdk/ndk

Path to the NDK. This property has been deprecated.

Configure your build @Android Developer User Guides

  • nativeDebug/asan.gradle
// use ```apply from: rootProject.file('nativeDebug/asan.gradle')``` in your root build gradle.

project.ext {
  useASAN = true
  Properties properties = new Properties()
  properties.load(project.rootProject.file('local.properties').newDataInputStream())
  sdkDir = properties.getProperty('sdk.dir') // for Android Studio 3.5
  // ndkDir = properties.getProperty('ndk.dir')
}
project.ext {
    useASAN = true
    ndkDir = properties.getProperty('ndk.dir')
}

Above version will get below error.

No signature of method: java.util.HashMap.getProperty() is applicable for argument types: (String) values: [ndk.dir]
Possible solutions: hasProperty(java.lang.String), getProperties()

How do I read properties defined in local.properties in build.gradle @StackOverflow

  • build.gradle
apply from: rootProject.file('nativeDebug/asan.gradle')
buildscript {}
allprojects {}
  • nativeDebug/asan_module.gradle
// use ```apply from: rootProject.file('nativeDebug/asan_module.gradle')``` in your module build gradle.

def abiFiltersForWrapScript = []
def SUPPORTED_ABI = ['arm64-v8a', 'armeabi-v7a', 'x86_64', 'x86']
def ASAN_LIB_LOCATION = 'asan/libs'
def WRAP_SCRIPT_LOCATION = 'asan/res'
def WRAP_SCRIPT_FILE_NAME = 'wrap.sh'

android {
  defaultConfig {
    externalNativeBuild {
      cmake {
        arguments "-DANDROID_ARM_MODE=arm", "-DANDROID_STL=c++_shared"
        abiFilters.addAll(SUPPORTED_ABI)
      }
    }
  }

  buildTypes {
    debug {
      packagingOptions {
        doNotStrip "**.so"
        // do not put unused library to APK.
        if (rootProject.ext.useASAN && abiFiltersForWrapScript) {
          def excludeAbi = ["armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64"]
              .findAll { !(it in abiFiltersForWrapScript) }
              .collect { "**/" + it + "/${WRAP_SCRIPT_FILE_NAME}" }
          println "exclude ${excludeAbi}"
          excludes += excludeAbi
        }
      }

      if (rootProject.ext.useASAN) {
        sourceSets {
          main {
            jniLibs { srcDir { ASAN_LIB_LOCATION } }
            resources { srcDir { WRAP_SCRIPT_LOCATION } }
          }
        }
      }
    }
  }
}

static def getAlias(abi) {
  if (abi == 'armeabi-v7a' || abi == 'armeabi') {
    return "arm"
  } else if (abi == "arm64-v8a") {
    return "aarch64"
  } else if (abi == "x86_64") {
    return "x86_64"
  } else if (abi == "x86") {
    return "i686"
  }
  return 'unknown'
}

task copyAsanLibs() {
  def sdkDir = rootProject.ext.sdkDir
  println "copyAsanLibs(): sdk.dir is " + sdkDir
  // def libDir = file("$rootProject.ext.ndkDir").absolutePath + "/toolchains/llvm/prebuilt/"
  for (String abi : SUPPORTED_ABI) {
    def dir = new File("app/${ASAN_LIB_LOCATION}/" + abi)
    dir.mkdirs()
    // FileTree tree = fileTree(dir: libDir).include("**/*asan*${getAlias(abi)}*.so")
    FileTree tree = fileTree(dir: sdkDir).include("**/*asan*${getAlias(abi)}*.so") // for Studio 3.5
    tree.each { File file ->
      println "${abi}: ${file.absolutePath} to ${dir.absolutePath}"
      copy {
        from file
        into dir.absolutePath
      }
    }
  }
}

static def generateHelpUtil(file) {
  // The NDK contains a recommended wrap.sh file for ASan
  // https://android.googlesource.com/platform/ndk/+/refs/heads/master/wrap.sh/asan.sh
  file.withWriter { writer ->
    writer.write('#!/system/bin/sh\n')
    writer.write('HERE="$(cd "$(dirname "$0")" && pwd)"\n')
    writer.write('export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1\n')
    writer.write('ASAN_LIB=$(ls $HERE/libclang_rt.asan-*-android.so)\n')
    writer.write('if [ -f "$HERE/libc++_shared.so" ]; then\n')
    writer.write('    # Workaround for https://github.com/android-ndk/ndk/issues/988.\n')
    writer.write('    export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so"\n')
    writer.write('else\n')
    writer.write('    export LD_PRELOAD="$ASAN_LIB"\n')
    writer.write('fi\n')
    writer.write('"$@"\n')
  }
}

task createAsanHelpUtilScript(dependsOn: copyAsanLibs) {
  println "\ngenerate ${WRAP_SCRIPT_FILE_NAME}"
  for (String abi : SUPPORTED_ABI) {
    def dir = new File("app/${WRAP_SCRIPT_LOCATION}/lib/" + abi)
    dir.mkdirs()
    def helpFile = new File(dir, WRAP_SCRIPT_FILE_NAME)
    generateHelpUtil(helpFile)
    println "${abi}: ${helpFile.path}"
  }
}

clean {
  delete new File("app/asan/").absolutePath
}
  • app/build.gradle
apply from: rootProject.file('nativeDebug/asan_module.gradle')

android {}
dependencies {}

Gradle - Error Copying Files To Project Root @StackOverflow

When I build the project, copyAsanLibs task will auto copy Asan to the specific folder and generate the sell file. The Asan library are located at:

$ pwd
/opt/Android/sdk/ndk-bundle/toolchains
$ find . -name "*asan*.so"
./llvm/prebuilt/darwin-x86_64/lib64/clang/8.0.7/lib/linux/libclang_rt.asan-aarch64-android.so
./llvm/prebuilt/darwin-x86_64/lib64/clang/8.0.7/lib/linux/libclang_rt.asan-arm-android.so
./llvm/prebuilt/darwin-x86_64/lib64/clang/8.0.7/lib/linux/libclang_rt.asan-i686-android.so
./llvm/prebuilt/darwin-x86_64/lib64/clang/8.0.7/lib/linux/libclang_rt.asan-x86_64-android.so
./llvm/prebuilt/darwin-x86_64/lib64/clang/8.0.7/lib/linux/libclang_rt.hwasan-aarch64-android.so
./llvm/prebuilt/darwin-x86_64/lib64/clang/8.0.7/lib/linux/libclang_rt.hwasan-x86_64-android.so
  • CMakeLists.txt
cmake_minimum_required(VERSION 3.10.2)

add_library(native-call-java-lib SHARED native-call-java-lib.cpp)

find_library(log-lib log)

target_link_libraries(native-call-java-lib ${log-lib})

if (USEASAN)
    target_compile_options(${TARGET} PUBLIC -fsanitize=address -fno-omit-frame-pointer)
    set_target_properties(${TARGET} PROPERTIES LINK_FLAGS -fsanitize=address)
endif (USEASAN)

Auto copy Asan library and create wrap.sh

Just sync or rebuild project, then it done.

$ tree --version
tree v1.7.0 (c) 1996 - 2014 by Steve Baker, Thomas Moore, Francesc Rocher, Florian Sesser, Kyosuke Tokoro
$ pwd
/Users/HSY/Desktop/NativeDemo
$ tree -C -P '*asan*.so|wrap.sh'
.
├── app
│   ├── asan
│   │   ├── libs
│   │   │   ├── arm64-v8a
│   │   │   │   ├── libclang_rt.asan-aarch64-android.so
│   │   │   │   └── libclang_rt.hwasan-aarch64-android.so
│   │   │   ├── armeabi-v7a
│   │   │   │   └── libclang_rt.asan-arm-android.so
│   │   │   ├── x86
│   │   │   │   └── libclang_rt.asan-i686-android.so
│   │   │   └── x86_64
│   │   │       ├── libclang_rt.asan-x86_64-android.so
│   │   │       └── libclang_rt.hwasan-x86_64-android.so
│   │   └── res
│   │       └── lib
│   │           ├── arm64-v8a
│   │           │   └── wrap.sh
│   │           ├── armeabi-v7a
│   │           │   └── wrap.sh
│   │           ├── x86
│   │           │   └── wrap.sh
│   │           └── x86_64
│   │               └── wrap.sh

Debug error

  • native-call-java-lib.cpp
#include <jni.h>
#include <cstdlib>

...

extern "C" JNIEXPORT void JNICALL
Java_com_practice_NativeCallJavaTest_test(JNIEnv *env, jobject) {
  int *ptr = (int *) malloc(sizeof(int) * 3);
  ptr[4] = 6; // here is line 16
}

Run the app, then below log show the error is heap-buffer-overflow and the ABI is x86.

  • LogCat
A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
A/DEBUG: Build fingerprint: 'Android/vbox86p/vbox86p:8.0.0/OPR6.170623.017/233:userdebug/test-keys'
A/DEBUG: Revision: '0'
A/DEBUG: ABI: 'x86'
A/DEBUG: pid: 2087, tid: 2087, name: com.practice  >>> com.practice <<<
A/DEBUG: signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
A/DEBUG: Abort message: '=================================================================
    ==2087==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xcdb1a5e0 at pc 0xbda43a8d bp 0xff8357f8 sp 0xff8357f0
    WRITE of size 4 at 0xcdb1a5e0 thread T0 (com.practice)
        #0 0xbda43a8c  (/data/app/com.practice-S7HmECG-7HxbYvgR8lFVqQ==/lib/x86/libnative-call-java-lib.so+0xa8c)
        #1 0xbe26f288  (/data/app/com.practice-S7HmECG-7HxbYvgR8lFVqQ==/oat/x86/base.odex+0xf288)

    0xcdb1a5e0 is located 4 bytes to the ri
A/DEBUG:     eax 00000000  ebx 00000827  ecx 00000827  edx 00000006
A/DEBUG:     esi 00000827  edi ff834d08
A/DEBUG:     xcs 00000023  xds 0000002b  xes 0000002b  xfs 0000006b  xss 0000002b
A/DEBUG:     eip f40a9ba9  ebp ff834d28  esp ff834cbc  flags 00000296
A/DEBUG: backtrace:
A/DEBUG:     #00 pc 00000ba9  [vdso:f40a9000] (__kernel_vsyscall+9)
A/DEBUG:     #01 pc 00075dcc  /system/lib/libc.so (tgkill+28)
A/DEBUG:     #02 pc 0001f05e  /system/lib/libc.so (abort+110)
A/DEBUG:     #03 pc 0005d4c1  /data/app/com.practice-S7HmECG-7HxbYvgR8lFVqQ==/lib/x86/libclang_rt.asan-i686-android.so (offset 0x52000) (_ZN11__sanitizer5AbortEv+81)
A/DEBUG:     #04 pc 0005c0bd  /data/app/com.practice-S7HmECG-7HxbYvgR8lFVqQ==/lib/x86/libclang_rt.asan-i686-android.so (offset 0x52000) (_ZN11__sanitizer3DieEv+189)
A/DEBUG:     #05 pc 000d79fb  /data/app/com.practice-S7HmECG-7HxbYvgR8lFVqQ==/lib/x86/libclang_rt.asan-i686-android.so (offset 0x52000) (_ZN6__asan19ScopedInErrorReportD2Ev+491)
A/DEBUG:     #06 pc 000d980a  /data/app/com.practice-S7HmECG-7HxbYvgR8lFVqQ==/lib/x86/libclang_rt.asan-i686-android.so (offset 0x52000) (_ZN6__asan18ReportGenericErrorEmmmmbmjb+1210)
A/DEBUG:     #07 pc 000da4a8  /data/app/com.practice-S7HmECG-7HxbYvgR8lFVqQ==/lib/x86/libclang_rt.asan-i686-android.so (offset 0x52000) (__asan_report_store4+40)
A/DEBUG:     #08 pc 00000a8c  /data/app/com.practice-S7HmECG-7HxbYvgR8lFVqQ==/lib/x86/libnative-call-java-lib.so (Java_com_practice_NativeCallJavaTest_test+188)
A/DEBUG:     #09 pc 0000f288  /data/app/com.practice-S7HmECG-7HxbYvgR8lFVqQ==/oat/x86/base.odex (offset 0xf000)
A/DEBUG:     #10 pc 0000307f  <anonymous:ce100000>
A/DEBUG:     #11 pc 0044db0f  /dev/ashmem/dalvik-main space (region space) (deleted)

I found the suspect is libnative-call-java-lib.so in backtrace part. And the crime scene is 00000a8c. Now I can ask addr2line to explain the case.

  • find tool in terminal
$ cd /opt/Android/sdk
$ pwd
/opt/Android/sdk
$ find . -name "*addr2line"
./ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-addr2line
./ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-addr2line
./ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android-addr2line
./ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-addr2line
./ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/i686-linux-android-addr2line
./ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/x86_64-linux-android-addr2line
./ndk-bundle/toolchains/x86-4.9/prebuilt/darwin-x86_64/bin/i686-linux-android-addr2line
./ndk-bundle/toolchains/x86_64-4.9/prebuilt/darwin-x86_64/bin/x86_64-linux-android-addr2line*
  • find libnative-call-java-lib.so
$ pwd
/Users/yen/Desktop/Native
$ find . -name "libnative-call-java-lib.so"
./app/build/intermediates/cmake/debug/obj/arm64-v8a/libnative-call-java-lib.so
./app/build/intermediates/cmake/debug/obj/armeabi-v7a/libnative-call-java-lib.so
./app/build/intermediates/cmake/debug/obj/x86/libnative-call-java-lib.so
./app/build/intermediates/cmake/debug/obj/x86_64/libnative-call-java-lib.so
./app/build/intermediates/transforms/mergeJniLibs/debug/0/lib/arm64-v8a/libnative-call-java-lib.so
./app/build/intermediates/transforms/mergeJniLibs/debug/0/lib/armeabi-v7a/libnative-call-java-lib.so
./app/build/intermediates/transforms/mergeJniLibs/debug/0/lib/x86/libnative-call-java-lib.so
./app/build/intermediates/transforms/mergeJniLibs/debug/0/lib/x86_64/libnative-call-java-lib.so
./app/build/intermediates/transforms/stripDebugSymbol/debug/0/lib/arm64-v8a/libnative-call-java-lib.so
./app/build/intermediates/transforms/stripDebugSymbol/debug/0/lib/armeabi-v7a/libnative-call-java-lib.so
./app/build/intermediates/transforms/stripDebugSymbol/debug/0/lib/x86/libnative-call-java-lib.so
./app/build/intermediates/transforms/stripDebugSymbol/debug/0/lib/x86_64/libnative-call-java-lib.so
  • Use x86 tool to check x86 .so
:Native yen$ /opt/Android/sdk/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/i686-linux-android-addr2line -C -i -f -e ./app/build/intermediates/transforms/mergeJniLibs/debug/0/lib/x86/libnative-call-java-lib.so 00000a8c
Java_com_practice_NativeCallJavaTest_test
/Users/yen/Desktop/Native/app/src/main/cpp/native-call-java-lib.cc:16

Address Sanitizer @Android Developer doc

应用与系统稳定性第七篇--- 用Asan 提前解决NDK疑难crash @簡書

results matching ""

    No results matching ""