AddressSanitizer (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 gấp đôi/Giải phóng đối tượng không phải bộ nhớ khối xếp
ASan chạy trên cả ARM 32-bit và 64-bit, cùng với x86 và x86-64. 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ớ lớn (tuỳ theo mô hình phân bổ, nhưng vào khoảng 2x).
Android 10 và nhánh phát hành mới nhất của AOSP trên AArch64 hỗ trợ Hardware-assisted AddressSanitizer (HWASan), một công cụ tương tự có mức sử dụng RAM thấp hơn và phạm vi phát hiện lỗi rộng hơn. Ngoài những lỗi do ASan phát hiện, HWASan còn phát hiện lỗi sử dụng ngăn xếp sau khi trả về.
HWASan có mức hao tổn CPU và kích thước mã tương tự, nhưng mức hao tổn RAM nhỏ hơn nhiều (15%). HWASan là không xác định. Chỉ có 256 giá trị thẻ có thể có, nên xác suất bỏ sót lỗi là 0,4%. HWASan không có các vùng màu đỏ có kích thước giới hạn của ASan để phát hiện tràn và vùng cách ly có dung lượng giới hạn để phát hiện lỗi sử dụng sau khi giải phóng, vì vậy, HWASan không quan tâm đến kích thước của lỗi tràn hoặc thời gian giải phóng bộ nhớ. Điều này giúp HWASan tốt hơn ASan. Bạn có thể đọc thêm về thiết kế của HWASan hoặc về việc sử dụng HWASan trên Android.
Ngoài lỗi tràn bộ nhớ khối xếp, ASan còn phát hiện lỗi tràn ngăn xếp/giá trị chung và có tốc độ nhanh với mức hao tổn bộ nhớ tối thiểu.
Tài liệu này mô tả cách tạo và chạy một phần/toàn bộ Android bằng ASan. Nếu bạn đang tạo một ứng dụng SDK/NDK bằng ASan, hãy xem Address Sanitizer.
Vệ sinh từng tệp thực thi bằng ASan
Thêm LOCAL_SANITIZE:=address
hoặc sanitize: { address: true }
vào quy tắc xây dựng cho tệp thực thi. Bạn có thể tìm kiếm mã để xem các ví dụ hiện có hoặc tìm các trình dọn dẹp khác có sẵn.
Khi phát hiện thấy lỗi, ASan sẽ in một báo cáo chi tiết vào cả đầu ra tiêu chuẩn và logcat
, sau đó làm hỏng quy trình.
Vệ sinh các thư viện dùng chung bằng ASan
Do cách ASan hoạt động, chỉ có thể dùng một thư viện được tạo bằng ASan bởi một tệp thực thi được tạo bằng ASan.
Để dọn dẹp một thư viện dùng chung được dùng trong nhiều tệp thực thi (không phải tất cả đều được tạo bằng ASan), bạn cần có 2 bản sao của thư viện. Cách được đề xuất để thực hiện việc này là thêm nội dung sau vào Android.mk
cho mô-đun có liên quan:
LOCAL_SANITIZE:=address LOCAL_MODULE_RELATIVE_PATH := asan
Thao tác này sẽ đặt thư viện vào /system/lib/asan
thay vì /system/lib
. Sau đó, hãy chạy tệp thực thi bằng cách dùng:
LD_LIBRARY_PATH=/system/lib/asan
Đối với các trình nền hệ thống, hãy thêm nội dung sau vào phần thích hợp của /init.rc
hoặc /init.$device$.rc
.
setenv LD_LIBRARY_PATH /system/lib/asan
Xác minh rằng quy trình đang sử dụng các thư viện từ /system/lib/asan
khi có bằng cách đọc /proc/$PID/maps
. Nếu không, bạn có thể cần tắt SELinux:
adb root
adb shell setenforce 0
# restart the process with adb shell kill $PID # if it is a system service, or may be adb shell stop; adb shell start.
Dấu vết ngăn xếp tốt hơn
ASan sử dụng một bộ tháo gỡ (unwinder) nhanh dựa trên con trỏ khung để ghi lại dấu vết ngăn xếp cho mọi sự kiện phân bổ và giải phóng bộ nhớ trong chương trình. Hầu hết Android đều được tạo mà không có con trỏ khung. Do đó, bạn thường chỉ nhận được một hoặc hai khung hình có ý nghĩa. Để khắc phục vấn đề này, hãy xây dựng lại thư viện bằng ASan (nên dùng!) hoặc bằng:
LOCAL_CFLAGS:=-fno-omit-frame-pointer LOCAL_ARM_MODE:=arm
Hoặc đặt ASAN_OPTIONS=fast_unwind_on_malloc=0
trong môi trường quy trình. Quá trình sau có thể dùng rất nhiều CPU, tuỳ thuộc vào mức tải.
Biểu tượng hoá
Ban đầu, các báo cáo ASan chứa thông tin tham chiếu đến các độ lệch trong tệp nhị phân và thư viện dùng chung. Có hai cách để lấy thông tin về tệp nguồn và dòng:
- Đảm bảo rằng tệp nhị phân
llvm-symbolizer
có trong/system/bin
.llvm-symbolizer
được tạo từ các nguồn trongthird_party/llvm/tools/llvm-symbolizer
. - Lọc báo cáo thông qua tập lệnh
external/compiler-rt/lib/asan/scripts/symbolize.py
.
Phương pháp thứ hai có thể cung cấp nhiều dữ liệu hơn (tức là file:line
vị trí) do có sẵn các thư viện được biểu thị trên máy chủ lưu trữ.
ASan trong ứng dụng
ASan không thể xem mã Java, nhưng có thể phát hiện lỗi trong các thư viện JNI. Để làm việc đó, bạn cần tạo tệp thực thi bằng ASan, trong trường hợp này là /system/bin/app_process(32|64)
. Điều này cho phép ASan trong tất cả ứng dụng trên thiết bị cùng một lúc, đây là một tải trọng lớn, nhưng thiết bị có RAM 2 GB sẽ có thể xử lý được.
Thêm LOCAL_SANITIZE:=address
vào quy tắc tạo app_process
trong frameworks/base/cmds/app_process
.
Chỉnh sửa phần service zygote
của tệp system/core/rootdir/init.zygote(32|64).rc
thích hợp để thêm các dòng sau vào khối dòng thụt lề có chứa class main
, cũng thụt lề cùng một lượng:
setenv LD_LIBRARY_PATH /system/lib/asan:/system/lib setenv ASAN_OPTIONS allow_user_segv_handler=true
Tạo, đồng bộ hoá adb, khởi động nhanh flash boot và khởi động lại.
Sử dụng thuộc tính wrap
Phương pháp trong phần trước sẽ đưa ASan vào mọi ứng dụng trong hệ thống (thực tế là vào mọi thành phần con của quy trình Zygote). Bạn chỉ có thể chạy một (hoặc một số) ứng dụng bằng ASan, đánh đổi một số chi phí bộ nhớ để khởi động ứng dụng chậm hơn.
Bạn có thể thực hiện việc này bằng cách khởi động ứng dụng bằng thuộc tính wrap.
.
Ví dụ sau đây chạy ứng dụng Gmail trong ASan:
adb root
adb shell setenforce 0 # disable SELinux
adb shell setprop wrap.com.google.android.gm "asanwrapper"
Trong ngữ cảnh này, asanwrapper
ghi lại /system/bin/app_process
thành /system/bin/asan/app_process
, được tạo bằng ASan. Thao tác này cũng thêm /system/lib/asan
vào đầu đường dẫn tìm kiếm thư viện động. Bằng cách này, các thư viện được đo lường bằng ASan từ /system/lib/asan
sẽ được ưu tiên hơn các thư viện thông thường trong /system/lib
khi chạy bằng asanwrapper
.
Nếu phát hiện thấy lỗi, ứng dụng sẽ gặp sự cố và báo cáo sẽ được in vào nhật ký.
SANITIZE_TARGET
Android 7.0 trở lên có hỗ trợ việc tạo toàn bộ nền tảng Android bằng ASan cùng một lúc. (Nếu bạn đang tạo một bản phát hành cao hơn Android 9, thì HWASan là lựa chọn phù hợp hơn.)
Chạy các lệnh sau trong cùng một cây bản dựng.
make -j42
SANITIZE_TARGET=address make -j42
Ở chế độ này, userdata.img
chứa các thư viện bổ sung và cũng phải được chuyển sang thiết bị. Sử dụng dòng lệnh sau:
fastboot flash userdata && fastboot flashall
Thao tác này sẽ tạo 2 nhóm thư viện dùng chung: nhóm bình thường trong /system/lib
(lệnh gọi make đầu tiên) và nhóm được ASan đo lường trong /data/asan/lib
(lệnh gọi make thứ hai). Các tệp thực thi từ bản dựng thứ hai sẽ ghi đè các tệp thực thi từ bản dựng đầu tiên. Các tệp thực thi được đo bằng ASan sẽ có một đường dẫn tìm kiếm thư viện khác bao gồm /data/asan/lib
trước /system/lib
thông qua việc sử dụng /system/bin/linker_asan
trong PT_INTERP
.
Hệ thống bản dựng sẽ xoá các thư mục đối tượng trung gian khi giá trị $SANITIZE_TARGET
thay đổi. Thao tác này buộc bạn phải tạo lại tất cả các mục tiêu trong khi vẫn giữ nguyên các tệp nhị phân đã cài đặt trong /system/lib
.
Bạn không thể tạo một số mục tiêu bằng ASan:
- Tệp thực thi được liên kết tĩnh
LOCAL_CLANG:=false
mục tiêuLOCAL_SANITIZE:=false
không phải là ASan choSANITIZE_TARGET=address
Các tệp thực thi như thế này sẽ bị bỏ qua trong bản dựng SANITIZE_TARGET
và phiên bản từ lệnh gọi make đầu tiên sẽ nằm trong /system/bin
.
Các thư viện như thế này được tạo mà không có ASan. Chúng có thể chứa một số mã ASan từ các thư viện tĩnh mà chúng phụ thuộc vào.