AddressSanitizer

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ớ khá lớn (tuỳ thuộc vào mô hình phân bổ, nhưng vào khoảng 2x).

Android 10 và nhánh chính AOSP trên AArch64 hỗ trợ AddressSanitizer được hỗ trợ phần cứng (HWASan), một công cụ tương tự có mức hao tổn RAM thấp hơn và phạm vi lỗi được phát hiện lớn hơn. Ngoài các lỗi do ASan phát hiện, HWASan còn phát hiện việc 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ó, vì vậy, xác suất bỏ lỡ lỗi là 0,4%. HWASan không có vùng màu đỏ có kích thước hạn chế của ASan để phát hiện tình trạng tràn bộ nhớ và vùng cách ly có dung lượng hạn chế để phát hiện lỗi use-after-free, vì vậy, HWASan không quan tâm đến mức độ tràn bộ nhớ hoặc thời gian bộ nhớ được phân bổ lại. Đ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ề cách sử dụng HWASan trên Android.

ASan phát hiện các lỗi tràn ngăn xếp/toàn cục và tràn vùng nhớ khối xếp và hoạt động 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 số/tất cả các phần của Android bằng ASan. Nếu bạn đang tạo ứng dụng SDK/NDK bằng ASan, hãy xem phần Address Sanitizer.

Dọn dẹp 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 bản dựng cho tệp thực thi. Bạn có thể tìm kiếm mã cho các ví dụ hiện có hoặc tìm các trình dọn dẹp có sẵn khác.

Khi phát hiện lỗi, ASan sẽ in một báo cáo chi tiết cho cả đầu ra tiêu chuẩn và logcat, sau đó làm hỏng quy trình.

Dọn dẹp các thư viện dùng chung bằng ASan

Do cách hoạt động của ASan, thư viện được tạo bằng ASan chỉ có thể được sử dụng 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ó hai bản sao của thư viện. Bạn nên 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:

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 (nếu có) bằng cách đọc /proc/$PID/maps. Nếu không, bạn có thể cần phải 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 chính xác hơn

ASan sử dụng một bộ tháo dỡ (unwinder) nhanh dựa trên con trỏ khung để ghi lại một 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 đượ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 tạo 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. Chính sách sau có thể dùng rất nhiều CPU, tuỳ thuộc vào tải.

Biểu tượng hoá

Ban đầu, báo cáo ASan chứa các tệp tham chiếu đến độ dời 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 tệp nhị phân llvm-symbolizer có trong /system/bin. llvm-symbolizer được tạo từ các nguồn trong third_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à các vị trí file:line) do có sẵn các thư viện được biểu tượng hoá trên máy chủ.

ASan trong ứng dụng

ASan không thể xem mã Java, nhưng có thể phát hiện lỗi trong thư viện JNI. Để làm được 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). Tính năng này bật ASan trong tất cả ứng dụng trên thiết bị cùng một lúc (tải nặng), nhưng thiết bị có RAM 2 GB có thể xử lý việc này.

Thêm LOCAL_SANITIZE:=address vào quy tắc bản dựng app_process trong frameworks/base/cmds/app_process. Bỏ qua mục tiêu app_process__asan trong cùng một tệp (nếu mục tiêu này vẫn còn tại thời điểm bạn đọc bài viết này).

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 các dòng thụt lề chứa class main, cũng thụt lề theo 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 bằng flashboot và khởi động lại.

Sử dụng thuộc tính gói

Phương pháp trong phần trước đặt 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 có thể chỉ chạy một (hoặc một số) ứng dụng bằng ASan, đánh đổi một số hao tổn 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 sẽ ghi đè /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 ở đầu đường dẫn tìm kiếm thư viện động. Bằng cách này, các thư viện đo lường ASan từ /system/lib/asan được ưu tiên hơn các thư viện thông thường trong /system/lib khi chạy với 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ý.

Hàm SANITIZE_TARGET

Android 7.0 trở lên 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 xây dựng 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 cài đặt ROM cho thiết bị. Sử dụng dòng lệnh sau:

fastboot flash userdata && fastboot flashall

Thao tác này sẽ tạo hai nhóm thư viện dùng chung: thông thường trong /system/lib (lệnh gọi tạo đầu tiên) và được đo lường bằng ASan trong /data/asan/lib (lệnh gọi tạo thứ hai). Các tệp thực thi của bản dựng thứ hai sẽ ghi đè các tệp thực thi của bản dựng đầu tiên. Các tệp thực thi được đo lường 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 xây dựng sẽ lưu trữ các thư mục đối tượng trung gian khi giá trị $SANITIZE_TARGET thay đổi. Thao tác này buộc tạo lại tất cả mục tiêu trong khi vẫn giữ lại các tệp nhị phân đã cài đặt trong /system/lib.

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
  • Mục tiêu LOCAL_CLANG:=false
  • LOCAL_SANITIZE:=false không được ASan cho SANITIZE_TARGET=address

Các tệp thực thi như vậy sẽ bị bỏ qua trong bản dựng SANITIZE_TARGET và phiên bản của lệnh gọi make đầu tiên sẽ được để lại trong /system/bin.

Các thư viện như vậy được tạo mà không cần ASan. Các tệp này 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.

Tài liệu hỗ trợ