Android NDK는 API 수준 27(Android O MR 1)부터 Address Sanitizer(ASan이라고도 함)를 지원합니다.
ASan은 네이티브 코드의 메모리 버그 감지를 위한 빠른 컴파일러 기반 도구입니다. ASan은 다음을 감지합니다.
- 스택 및 힙 버퍼 오버플로우/언더플로우
- 프리 후 힙 사용
- 범위를 벗어난 스택 사용
- 더블 프리/와일드 프리
ASan의 CPU 오버헤드는 약 2x이고 코드 크기 오버헤드는 50%와 2x 사이이며 메모리 오버헤드는 대규모(할당 패턴에 따라 다르지만 대략 2x 정도)입니다.
샘플 앱
샘플 앱에서 Asan용 빌드 변형을 구성하는 방법을 확인할 수 있습니다.
빌드
Address Sanitizer를 사용하여 앱의 네이티브(JNI) 코드를 빌드하려면 다음을 따르세요.
ndk-build
Application.mk에서 다음 코드 추가
APP_STL := c++_shared # Or system, or none.
APP_CFLAGS := -fsanitize=address -fno-omit-frame-pointer
APP_LDFLAGS := -fsanitize=address
Android.mk의 각 모듈에 다음 코드 추가
LOCAL_ARM_MODE := arm
CMake
모듈의 build.gradle에서 다음 코드 추가
android {
defaultConfig {
externalNativeBuild {
cmake {
// Can also use system or none as ANDROID_STL.
arguments "-DANDROID_ARM_MODE=arm", "-DANDROID_STL=c++_shared"
}
}
}
}
CMakeLists.txt의 각 타겟에 다음 코드 추가
target_compile_options(${TARGET} PUBLIC -fsanitize=address -fno-omit-frame-pointer)
set_target_properties(${TARGET} PROPERTIES LINK_FLAGS -fsanitize=address)
실행
Android O MR1(API 수준 27)부터 애플리케이션은 애플리케이션 프로세스를 래핑하거나 대체할 수 있는 래핑 셸 스크립트를 제공할 수 있습니다. 이 스크립트를 통해 디버그 가능한 애플리케이션에서 애플리케이션 시작을 맞춤설정하여 프로덕션 기기에서 ASan을 사용할 수 있습니다.
- 애플리케이션 매니페스트에
android:debuggable
을 추가합니다. - 앱의
build.gradle
파일에서useLegacyPackaging
을true
로 설정합니다. 자세한 내용은 래핑 셸 스크립트를 참고하세요. - ASan 런타임 라이브러리를 앱 모듈의
jniLibs
에 추가합니다. src/main/resources/lib
디렉터리의 각 디렉터리에 다음 콘텐츠가 포함된wrap.sh
파일을 추가합니다.#!/system/bin/sh HERE="$(cd "$(dirname "$0")" && pwd)" export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1 ASAN_LIB=$(ls $HERE/libclang_rt.asan-*-android.so) if [ -f "$HERE/libc++_shared.so" ]; then # Workaround for https://github.com/android-ndk/ndk/issues/988. export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so" else export LD_PRELOAD="$ASAN_LIB" fi "$@"
프로젝트의 애플리케이션 모듈 이름이 app
이라고 가정하면 최종 디렉터리 구조에는 다음이 포함되어야 합니다.
<project root>
└── app
└── src
└── main
├── jniLibs
│ ├── arm64-v8a
│ │ └── libclang_rt.asan-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
└── resources
└── lib
├── arm64-v8a
│ └── wrap.sh
├── armeabi-v7a
│ └── wrap.sh
├── x86
│ └── wrap.sh
└── x86_64
└── wrap.sh
스택 트레이스
Address Sanitizer는 모든 malloc
/realloc
/free
호출에서 스택을 해제해야 합니다. 다음과 같은 두 가지 옵션이 있습니다.
'고속' 프레임 포인터 기반 언와인더: 빌드 섹션의 지침에 따라 사용되는 옵션입니다.
'저속' CFI 언와인더: 이 모드에서 ASan은
_Unwind_Backtrace
를 사용합니다.-funwind-tables
만 필요하며 보통은 기본적으로 사용 설정되어 있습니다.
고속 언와인더는 malloc/realloc/free의 기본값입니다. 저속 언와인더는 치명적인 스택 트레이스의 기본값입니다. wrap.sh의 ASAN_OPTIONS
변수에 fast_unwind_on_malloc=0
을 추가하여 모든 스택 트레이스에 저속 언와인더를 사용 설정할 수 있습니다.