Android NDK hỗ trợ Address Sanitizer (còn gọi là ASan) kể từ API cấp 27 (Android O MR 1).
ASan là một công cụ dựa trên trình biên dịch giúp phát hiện các lỗi bộ nhớ trong mã gốc. ASan phát hiện:
- Chặn tràn (overflow)/chặn trống (underflow) cho ngăn xếp (stack) và bộ nhớ khối xếp (heap)
- Sử dụng bộ nhớ khối xếp sau khi giải phóng
- Sử dụng ngăn xếp bên ngoài phạm vi
- Giải phóng hai lần/giải phóng bộ nhớ chưa được cấp phát trước đó
Mức hao tổn CPU của ASan là khoảng 2x, mức hao tổn kích thước mã trong khoảng từ 50% đến 2x và mức hao tổn bộ nhớ khá lớn (tuỳ theo mô hình phân bổ, nhưng vào khoảng 2x).
Ứng dụng mẫu
Ứng dụng mẫu cho biết cách định cấu hình biến thể bản dựng cho ASan.
Tạo
Để tạo mã gốc (JNI) của ứng dụng bằng Address Sanitizer, hãy làm như sau:
ndk-build
Trong Application.mk:
APP_STL := c++_shared # Or system, or none.
APP_CFLAGS := -fsanitize=address -fno-omit-frame-pointer
APP_LDFLAGS := -fsanitize=address
Đối với mỗi mô-đun trong Android.mk:
LOCAL_ARM_MODE := arm
CMake
Trong tệp build.gradle của mô-đun:
android {
defaultConfig {
externalNativeBuild {
cmake {
// Can also use system or none as ANDROID_STL.
arguments "-DANDROID_ARM_MODE=arm", "-DANDROID_STL=c++_shared"
}
}
}
}
Đối với mỗi mục tiêu trong CMakeLists.txt:
target_compile_options(${TARGET} PUBLIC -fsanitize=address -fno-omit-frame-pointer)
set_target_properties(${TARGET} PROPERTIES LINK_FLAGS -fsanitize=address)
Chạy
Kể từ Android O MR1 (API cấp 27), một ứng dụng có thể cung cấp tập lệnh bao bọc môi trường shell có khả năng bao bọc hoặc thay thế quy trình của ứng dụng. Nhờ đó, một ứng dụng có thể gỡ lỗi có khả năng tuỳ chỉnh hoạt động khởi động ứng dụng, cho phép sử dụng ASan trên các thiết bị phát hành chính thức.
- Thêm
android:debuggable
vào tệp kê khai ứng dụng. - Đặt
useLegacyPackaging
thànhtrue
trong tệpbuild.gradle
của ứng dụng. Xem hướng dẫn về tập lệnh bao bọc môi trường shell để biết thêm thông tin. - Thêm thư viện thời gian chạy ASan vào
jniLibs
của mô-đun ứng dụng. Thêm tệp
wrap.sh
có nội dung sau vào mỗi thư mục trong thư mụcsrc/main/resources/lib
.#!/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 "$@"
Giả sử mô-đun ứng dụng của dự án có tên là app
, cấu trúc thư mục cuối cùng phải bao gồm các thông tin sau:
<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
Dấu vết ngăn xếp
Address Sanitizer cần gỡ bỏ ngăn xếp trong mọi lệnh gọi malloc
/realloc
/free
. Có hai cách:
Bộ gỡ bỏ dựa trên con trỏ khung "fast" (nhanh). Bạn sẽ sử dụng công cụ này theo hướng dẫn trong phần xây dựng.
Bộ gỡ bỏ CFI "slow" (chậm). Ở chế độ này, ASan sử dụng
_Unwind_Backtrace
. Phương thức này chỉ yêu cầu-funwind-tables
(thường được bật theo mặc định).
Bộ gỡ bỏ nhanh là tuỳ chọn mặc định cho malloc/realloc/free. Bộ gỡ bỏ chậm là tuỳ chọn mặc định cho dấu vết ngăn xếp quan trọng. Bạn có thể bật bộ gỡ bỏ chậm cho tất cả dấu vết ngăn xếp bằng cách thêm fast_unwind_on_malloc=0
vào biến ASAN_OPTIONS
trong wrap.sh.