AddressSanitizer

AddressSanitizer (ASan) 是一款以編譯器為基礎的快速工具,用於偵測原生程式碼中的記憶體錯誤。

ASan 可偵測到:

  • 堆疊和堆積緩衝區溢位/反向溢位
  • 釋放後的堆積使用情況
  • 超出範圍的堆疊使用情況
  • 重複釋放/錯誤釋放

ASan 可以在 32 位元和 64 位元 ARM 以及 x86 和 x86-64 上執行。ASan 的 CPU 負擔約為 2 倍,程式碼大小負擔介於 50% 到 2 倍之間,記憶體負擔也相當大 (取決於您的配置模式,但約為 2 倍)。

Android 10 和 AArch64 上的 AOSP 主分支支援 Hardware-assisted AddressSanitizer (HWASan),這是一款類似的工具,可降低 RAM 額外負擔,並偵測到更多範圍的錯誤。除了 ASan 偵測到的錯誤,HWASan 還會偵測回傳後的堆疊使用情形。

HWASan 的 CPU 和程式碼大小大致相同,但 RAM 負擔更低 (15%)。 HWASan 無法確定。標記值可能只有 256 個,因此固定為 0.4% 遺漏任何錯誤的機率HWASan 沒有 ASan 的限量紅色區域 偵測溢位和有限容量的隔離區 藉此偵測釋放後的使用情況 所以無論 HWASan 要得知溢位有多大 被取消配置。因此 HWASan 優於 ASan。如要進一步瞭解 設計 HWASan,或是有關 Android 上的 HWASan 使用方式。

ASan 除了偵測堆積溢位外,還會偵測堆疊/全域溢位,而且速度快且記憶體負擔低。

本文件說明如何使用 ASan 建構及執行部分/所有 Android 元件。如果您要使用 ASan 建構 SDK/NDK 應用程式,請改為參閱「Address Sanitizer」。

使用 ASan 清理個別執行檔

新增LOCAL_SANITIZE:=addresssanitize: { address: true }到 執行檔的建構規則您可以在程式碼中搜尋現有範例,或尋找 其他可用的衛生器

偵測到錯誤時,ASan 會向標準版本列印詳細報告 並寫入 logcat,然後異常終止程序。

使用 ASan 清理共用程式庫

由於 ASan 的運作方式,使用 ASan 建構的程式庫只能由使用 ASan 建構的可執行檔使用。

如要清理用於多個執行檔的共用程式庫,而非所有執行檔 是使用 ASan 建構的,因此需要兩個程式庫副本。建議您為相關模組在 Android.mk 中加入下列內容:

LOCAL_SANITIZE:=address
LOCAL_MODULE_RELATIVE_PATH := asan

這會導致程式庫放在 /system/lib/asan,而非 /system/lib。接著,使用以下指令執行執行檔:

LD_LIBRARY_PATH=/system/lib/asan

若是系統 Daemon,請將下列程式碼新增到 /init.rc/init.$device$.rc

setenv LD_LIBRARY_PATH /system/lib/asan

確認程序使用的是 /system/lib/asan 的程式庫 進行簡報時,讀取 /proc/$PID/maps。如果不是,您可能需要停用 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.

改善堆疊追蹤

ASan 使用以影格指標為基礎的快速解開器記錄堆疊 程式中每個記憶體配置和取消配置事件的追蹤記錄。大多數 Android 都是在沒有影格指標的情況下建構。因此 只產生一兩個有意義的影格如要修正這個問題,請使用 ASan (建議做法) 或以下任一做法重新建構程式庫:

LOCAL_CFLAGS:=-fno-omit-frame-pointer
LOCAL_ARM_MODE:=arm

或者,您也可以在處理程序環境中設定 ASAN_OPTIONS=fast_unwind_on_malloc=0。後者可能會耗用大量 CPU 資源,具體取決於負載。

符號化

一開始,ASan 報表會包含對二進位檔和共用程式庫中偏移值的參照。取得來源檔案和行資訊的方式有兩種:

  • 確認 llvm-symbolizer 二進位檔位於 /system/bin 中。 「llvm-symbolizer」是以 third_party/llvm/tools/llvm-symbolizer
  • 使用 external/compiler-rt/lib/asan/scripts/symbolize.py篩選報表 指令碼

由於主機上有符號化程式庫,因此第二種方法可以提供更多資料 (即 file:line 位置)。

應用程式中的 ASan

ASan 無法查看 Java 程式碼,但可以偵測 JNI 程式庫中的錯誤。為此,您必須使用 ASan 建構可執行檔,在本例中為 /system/bin/app_process(32|64)。這會同時在裝置上的所有應用程式中啟用 ASan,這會造成大量負載,但裝置的 RAM 若有 2 GB,應該可以處理這項作業。

LOCAL_SANITIZE:=address新增至 frameworks/base/cmds/app_process 中的 app_process 建構規則。目前請忽略同一個檔案中的 app_process__asan 目標 (如果您閱讀本文時仍存在)。

編輯適當 system/core/rootdir/init.zygote(32|64).rc 檔案的 service zygote 部分,將下列行加入包含 class main 的縮排行區塊,並以相同的縮排量縮排:

    setenv LD_LIBRARY_PATH /system/lib/asan:/system/lib
    setenv ASAN_OPTIONS allow_user_segv_handler=true

建構、ADB 同步、Fastboot 閃燈開機,然後重新啟動。

使用 wrap 屬性

上一節的方法會將 ASan 加入系統中的每個應用程式 (實際上是 Zygote 程序的每個後代)。您可以使用 ASan 執行一個或多個應用程式,以便在應用程式啟動速度較慢的情況下,換取部分記憶體開銷。

方法是使用 wrap. 屬性啟動應用程式。以下範例會在 ASan 下執行 Gmail 應用程式:

adb root
adb shell setenforce 0  # disable SELinux
adb shell setprop wrap.com.google.android.gm "asanwrapper"

在這個情況下,asanwrapper 會將 /system/bin/app_process 重寫為 /system/bin/asan/app_process,後者是使用 ASan 建構的。也會在動態程式庫搜尋路徑的開頭新增 /system/lib/asan。以這樣 ASan 檢測 較推薦使用 /system/lib/asan 的程式庫至一般程式庫 使用 asanwrapper 執行時,在 /system/lib 中執行。

如果發現錯誤,應用程式就會當機,且報告會顯示於 。

SANITIZE_TARGET

Android 7.0 以上版本支援使用 ASan 一次建構整個 Android 平台。(如果您要建構的版本高於 Android 9,建議您選擇 HWASan)。

在同一個建構樹狀結構中執行下列指令。

make -j42
SANITIZE_TARGET=address make -j42

在這個模式下,userdata.img 包含額外的程式庫,必須 刷新到裝置上使用以下指令列:

fastboot flash userdata && fastboot flashall

這樣會建立兩組共用程式庫:一般的 /system/lib (第一次發出叫用) 以及 ASan 檢測 /data/asan/lib (第二次發出叫用)。從 第二個版本會覆寫第一個版本經過 ASan 檢測的執行檔會透過 PT_INTERP 中的 /system/bin/linker_asan 取得不同的程式庫搜尋路徑,其中包含 /data/asan/lib/system/lib

$SANITIZE_TARGET 值變更時,建構系統會覆寫中繼物件目錄。這會強制重建所有目標,同時保留 /system/lib 下已安裝的二進位檔。

部分目標無法使用 ASan 建構:

  • 靜態連結的執行檔
  • LOCAL_CLANG:=false 目標
  • LOCAL_SANITIZE:=false不是「SANITIZE_TARGET=address」的 ASan

SANITIZE_TARGET 建構作業會略過這類執行檔,而且 第一次叫用的版本會保留在 /system/bin 中。

這類程式庫是在沒有 ASan 的情況下建構。這些檔案可能包含部分 ASan 程式碼,這些程式碼來自其依附的靜態資料庫。

佐證文件