瞭解 HWASan 報告

HWASan 工具偵測到記憶體錯誤時,程序會以 abort() 終止,並將報告輸出至 stderr 和 Logcat。與 Android 上的所有原生當機情形一樣,HWASan 錯誤會顯示在 /data/tombstones 下方。

範例報表

相較於一般原生異常終止,HWASan 會在墓碑頂端附近的「Abort message」(中止訊息) 欄位中,提供額外資訊。以下是堆積區塊當機的範例。如要瞭解堆疊錯誤,請參閱堆疊專屬章節的附註

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'google/flame_hwasan/flame:Tiramisu/MASTER/7956676:userdebug/dev-keys'
Revision: 'DVT1.0'
ABI: 'arm64'
Timestamp: 2019-04-24 01:13:22+0000
pid: 11154, tid: 11154, name: sensors@1.0-ser  >>> /vendor/bin/hw/android.hardware.sensors@1.0-service <<<
signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
Abort message: '

[...]

[0x00433ae20040,0x00433ae20060) is a small unallocated heap chunk; size: 32 offset: 5








[ … regular crash dump follows …]

這與 AddressSanitizer 報告類似。 與上述工具不同的是,幾乎所有 HWASan 錯誤都是標記不符的錯誤,也就是指標標記與對應記憶體標記不符的記憶體存取。這可以是下列任一項目:

  • 堆疊或堆積上的界外存取
  • 堆積上的 Use-after-free 錯誤
  • 堆疊上的回傳後使用錯誤

版面

以下說明 HWASan 報告的各個部分。

存取錯誤

內含錯誤記憶體存取的相關資訊,包括:

  • 存取類型 (READWRITE)
  • 存取大小 (嘗試存取的位元組數)
  • 存取的執行緒編號
  • 指標和記憶體標記 (適用於進階偵錯)

存取堆疊追蹤記錄

錯誤記憶體存取的堆疊追蹤。如要符號化,請參閱「符號化」。

原因

存取權不佳的可能原因。如果有多個候選人,系統會依可能性由高到低列出。潛在原因的詳細資訊。HWASan 可以診斷下列原因:

  • 釋放後的堆積使用情況
  • 堆疊標記不符,可能是傳回後使用堆疊、超出範圍後使用堆疊,或超出界限
  • 堆積緩衝區溢位
  • 全域溢位

記憶體資訊

說明 HWASan 對存取記憶體的瞭解程度,可能因錯誤類型而異:

錯誤類型 原因 報表格式
標記不符 釋放後的堆積使用情況 請使用下列報表格式:
<address> is located N bytes inside of M-byte region [<start>, <end>)
freed by thread T0 here:
堆積緩衝區溢位 請注意,這也可能是溢位。
<address> is located N bytes to the right of M-byte region [<start>, <end>)
allocated here:
堆疊標記不符 堆疊報表不會區分溢位或反向溢位,以及回傳後使用錯誤。此外,如要找出導致錯誤的堆疊配置,必須執行離線符號化步驟。請參閱「瞭解堆疊報表」。
無效的免費 釋放後的堆積使用情況 重複釋放錯誤。如果程序關閉時發生這種情況,可能表示違反 ODR 規定
<address> is located N bytes inside of M-byte region [<start>, <end>)
freed by thread T0 here:
無法描述地址 可能是任意釋放 (釋放先前未分配的記憶體),或是分配的記憶體從 HWASan 的釋放緩衝區中逐出後,發生雙重釋放。
0x... 是 HWAsan 陰影記憶體 應用程式嘗試釋放 HWASan 內部的記憶體,因此發生任意釋放。

解除分配堆疊追蹤

記憶體解除分配位置的堆疊追蹤。僅適用於釋放後使用或無效釋放的錯誤。如要符號化,請參閱「符號化」。

配置堆疊追蹤

記憶體配置位置的堆疊追蹤。 如要符號化,請參閱「符號化」。

進階偵錯資訊

HWASan 報告也提供一些進階偵錯資訊,包括 (依序):

  1. 程序中的執行緒清單
  2. 程序中的執行緒清單
  3. 故障記憶體附近的記憶體標記值
  4. 記憶體存取點的暫存器傾印

記憶體標記傾印

您可以使用標記記憶體傾印,尋找與指標標記具有相同標記的附近記憶體配置。這些標記可能會指向具有大型偏移的越界存取。一個標記對應 16 位元組的記憶體;指標標記是位址的前 8 位元。標記記憶體傾印可提供提示,例如以下是向右的緩衝區溢位:

tags: ad/5c (ptr/mem)
[...]
Memory tags around the buggy address (one tag corresponds to 16 bytes):
  0x006f33ae1ff0: 0e  0e  0e  57  20  20  20  20  20  2e  5e  5e  5e  5e  5e  b5
=>0x006f33ae2000: f6  f6  f6  f6  f6  4c  ad  ad  ad  ad  ad  ad [5c] 5c  5c  5c
  0x006f33ae2010: 5c  04  2e  2e  2e  2e  2e  2f  66  66  66  66  66  80  6a  6a
Tags for short granules around the buggy address (one tag corresponds to 16 bytes):
  0x006f33ae1ff0: ab  52  eb  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..
=>0x006f33ae2000: ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  .. [..] ..  ..  ..
  0x006f33ae2010: ..  5c  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..

請注意左側的 6 × 16 = 96 位元組 ad 標記執行,與指標標記相符。

如果分配的大小不是 16 的倍數,則大小的餘數會儲存為記憶體標記,而標記會儲存為短顆粒標記。在上一個範例中,緊接在以粗體標記的分配項目 ad 之後,我們有標記 5c 的 5 × 16 + 4 = 84 位元組分配項目。

零記憶體標記 (例如 tags: ad/00 (ptr/mem)) 表示有「從堆疊傳回後使用」錯誤。

暫存器傾印

HWASan 報告中的暫存器傾印會對應到執行無效記憶體存取的指令。這個傾印檔後面接著來自一般 Android 信號處理常式的另一個暫存器傾印檔。請忽略第二個傾印,因為這是 HWASan 呼叫 abort() 時擷取的,與錯誤無關。

符號化

如要在堆疊追蹤中取得函式名稱和行號 (並取得用於範圍後使用錯誤的變數名稱),需要離線符號化步驟。

首次設定:安裝 llvm-symbolizer

如要進行符號化,系統必須安裝 llvm-symbolizer,且可從 $PATH 存取。在 Debian 上,您可以使用 sudo apt install llvm 安裝。

取得符號檔

如要符號化,我們需要含有符號的未剝除二進位檔。這些檔案的位置取決於建構類型:

  • 如果是本機版本,符號檔案位於 out/target/product/<product>/symbols/
  • 如果是 AOSP 建構版本 (例如透過 Android Flash Tool 刷入),建構版本會位於 Android CI。在建構的「構件」中,會有 ${PRODUCT}-symbols-${BUILDID}.zip 檔案。
  • 如要取得貴機構的內部版本,請參閱貴機構的文件,瞭解如何取得符號檔。

Symbolize

hwasan_symbolize --symbols <DECOMPRESSED_DIR>/out/target/product/*/symbols < crash

瞭解堆疊報表

如果錯誤發生在堆疊變數中,HWASan 報告會包含下列詳細資料:

Cause: stack tag-mismatch
Address 0x007d4d251e80 is located in stack of thread T64
Thread: T64 0x0074000b2000 stack: [0x007d4d14c000,0x007d4d255cb0) sz: 1088688 tls: [0x007d4d255fc0,0x007d4d259000)
Previously allocated frames:
  record_addr:0x7df7300c98 record:0x51ef007df3f70fb0  (/apex/com.android.art/lib64/libart.so+0x570fb0)
  record_addr:0x7df7300c90 record:0x5200007df3cdab74  (/apex/com.android.art/lib64/libart.so+0x2dab74)
  [...]

為協助您瞭解堆疊錯誤,HWASan 會追蹤過去的堆疊框架。HWASan 不會將這些內容轉換為錯誤報告中可供人解讀的內容,且需要額外的符號化步驟

ODR 違規

HWASan 報告的部分 use-after-free 錯誤可能表示違反「單一定義規則」(ODR)。如果同一個程式中多次定義相同變數,就會發生 ODR 違規情形。 這也表示變數會多次解構,可能導致 use-after-free 錯誤。

符號化後,ODR 違規事項會在無效存取堆疊和「freed here」堆疊上,顯示含有 __cxa_finalize 的 use-after-free 錯誤。先前在此處分配的堆疊包含 __dl__ZN6soinfo17call_constructorsEv,且應指向程式中定義堆疊上較高變數的位置。

如果使用靜態程式庫,可能會違反 ODR。如果定義 C++ 全域變數的靜態資料庫連結至多個共用程式庫或執行檔,則相同符號的多個定義可能會存在於相同位址空間,導致 ODR 錯誤。

疑難排解

本節說明一些錯誤和解決方法。

HWAddressSanitizer 無法更詳細地說明位址

有時 HWASan 可能會用盡空間,無法儲存過去記憶體配置的相關資訊。在這種情況下,報告只會包含立即記憶體存取的堆疊追蹤記錄,後面會附註:

HWAddressSanitizer can not describe address in more detail.

在某些情況下,多次執行測試即可解決問題。另一種做法是增加 HWASan 記錄大小。你可以在 build/soong/cc/sanitize.go 中全域執行這項操作 (尋找 hwasanGlobalOptions),也可以在程序環境中執行 (嘗試 adb shell echo $HWASAN_OPTIONS 查看目前設定)。

如果存取的記憶體未對應,或由非 HWASan 感知的分配器分配,也可能發生這項錯誤。在這種情況下,當機標頭中列出的 mem 標記通常是 00。如果您可以存取完整的墓碑,建議您參考記憶體對應傾印,找出地址所屬的對應 (如有)。

同一執行緒中的巢狀錯誤

這表示產生 HWASan 損毀報告時發生錯誤。這通常是因為 HWASan 執行階段發生錯誤。提出錯誤報告,並盡可能提供重現問題的操作說明。