AddressSanitizer

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

ASan 可偵測到:

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

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

Android 10 和 AArch64 上的 Android 開放原始碼計畫主要分支版本 支援硬體輔助 AddressSanitizer (HWASan), 是一種類似工具,RAM 負擔較小,且在記憶體容量較大 各種錯誤HWASan 可以偵測返回後的堆疊使用行為、找出錯誤 與 ASan 偵測。

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

ASan 可偵測堆疊/全域溢位 除了堆積溢位以外,速度飛快且記憶體負擔最低。

本文件將說明如何使用 ASan。如果您使用 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, 但搭載 2 GB RAM 的裝置應該就能處理這個負載

LOCAL_SANITIZE:=address新增至 frameworks/base/cmds/app_process 中的 app_process 建構規則。忽略 app_process__asan 目標目前位於相同檔案 (如果有的話) 您讀到這段文字時依然存在

編輯以下項目的「service zygote」部分: 適當的 system/core/rootdir/init.zygote(32|64).rc 檔案來新增 將下列幾行複製到包含 class main 的縮排行區塊中, 以相同的數量縮排:

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

建構、ADB 同步、Fastboot 快閃開機和重新啟動。

使用包裝屬性

上一節的方法會將 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 中執行。

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

目標

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

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

make -j42
SANITIZE_TARGET=address make -j42

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

fastboot flash userdata && fastboot flashall

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

當建構系統擷取到 $SANITIZE_TARGET的值已變更。這會強制使 並保留 /system/lib 底下已安裝的二進位檔。

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

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

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

這類程式庫是在沒有 ASan 的情況下建構。可能包含一些 ASan 所需的靜態資料庫編寫程式碼

佐證文件