Zircon 中的跨翻譯單元靜態分析

本文件說明:

  • 如何在 Zircon 中使用 Clang Static Analyzer (CSA) 設定跨翻譯單位分析 (CTU);
  • 由 Kareem Khazem 在實習期間完成的工作;以及
  • 完成剩下的工作,才能讓 Zircon 完整支援 CTU。

在 Zircon 上設定並執行 CTU

摘要:下載 Clang 來源,並在編譯前套用數個非主要修補程式。在分析工具周圍執行我的包裝函式指令碼。下載 CodeChecker 工具;使用這項工具摘要分析結果,並啟動網路伺服器,以便使用網頁介面查看結果。

啟用 CTU 修補程式

您需要留意以下兩個修補程式集:

  • Samsung 修補程式集,這是為 Clang 新增 AST 合併支援的龐大修補程式。主要由 lib/AST/ASTImporter.cpp 的新增內容組成。此外,tools/xtu-build/* 下也有一組 (原始,成效不彰) 的 CTU 分析工具。這個修補程式組以 Clang 舊有版本為基礎,而且這個大小和規模相當大,導致要把批發的批發到樹狀結構上 (ToT) 的 Clang 上非常困難。
  • Ericsson 修補程式集內含一部分 Samsung 的 AST 合併工作,同時也新增了幾項可進行 CTU 分析的新工具 (tools/xtu-build-new/*tools/scan-build-py/*)。xtu-build-new 工具改善了 Samsung 的 xtu-build 工具,而且與上述工具略有不同。這個修補程式組比 Samsung One 還新,作者正在努力確保以 ToT 為基礎。

我們將與 Ericsson 的修補程式組修補 Clang,因為 AST 的合併工作基底已乾淨,我們也取得了新版分析工具。不過請注意,針對 Zircon 的 CTU 支援並不完整;在某些情況下,Samsung 修補程式組中的程式碼會提供必要功能 (詳情請見下文)。

建構支援 CTU 的 CSA 的步驟

  1. 照常下載及建構 Clang 和 LLVM。
  2. 在其他目錄中複製 Ericsson 的 Clang 分支,然後切換至 ctu-master 分支版本。
  3. 這段指令碼下載至 Ericsson 的分支中並執行。程式應將一系列修補程式轉儲到修補程式目錄。我刻意只傾印 Ericsson 做出的變更到 1bb3636 (1bb3636) 後,在實習期間最新的修訂版本。

    • 如果您想要查看 Ericsson 的最新變更內容,可以嘗試在指令碼中將 1bb3636 改為 HEAD。在指令碼中指定其他範圍,請務必略過將上游修訂版本合併至 ctu-master 分支版本的修訂版本。git log --graph 有助於判斷上游修訂版本與 Ericsson 的修訂版本。
    git log --graph  --decorate --date=relative --format=format:'%C(green)%h%C(yellow) %s%C(reset)%w(0,6,6)%C(bold green)\n%C(cyan)%G? %C(bold red)%aN%C(reset) %cr%C(reset)%w(0,0,0)\n%-D\n%C(reset)' --all
    
  4. 一次將產生的修補程式套用至上游 Clang (而非 Ericsson fork)。

    for p in $(ls $PATCH_DIR/*.patch | sort -n); do git am < $p; done
    
  5. 套用下方列出的 Kareem Khazem 修補程式 (如果尚未送達)

  6. 重新建構上游 Clang 和 LLVM。

執行 CTU 分析

摘要:執行包裝函式指令碼。這會正常建構 Zircon,然後再次建構,而是傾印序列化的 AST (而非物件檔案),最後使用傾印的 AST 分析每個檔案,以達成 CTU。

CTU 的運作方式

由此向後述說故事:

非 CTU 靜態分析會分析每個 TU 的 AST;凡是對外部函式發出的函式呼叫均視為不透明。儘管如此,CTU 分析仍會設法將不透明函式呼叫節點「替換成」該函式實作的 AST。

因此,CTU 分析會照常開始分析 AST,但只要遇到函式呼叫節點,就會嘗試在 AST 中合併該函式的 AST。這需要事先將函式序列化到磁碟的 AST,以便分析器將 AST 重新載入記憶體。這個程式庫也仰賴支援 AST 合併,也就是針對 ASTImporter.cpp 的 Samsung 修補程式 (以及衍生自的 Ericsson 修補程式)。

為了將 AST 序列化到磁碟,我們需要模擬實際的建構程序。做法是在記錄編譯器叫用時實際執行 Zircon 的實際版本;這可讓我們「播放」叫用,但在編譯器旗標修改後會轉儲 AST 檔案,而不是物件檔案。

總結來說,這次轉寄:

  • 使用 Clang 建構 Zircon,並且將建構程序納入 bear 等程式中,以記錄編譯器叫用內容,並產生 JSON 編譯資料庫。
  • 重播相同的編譯步驟,但轉儲 AST 檔案而非物件檔案。這是 xtu-build.py 工具的功能。
  • 照常執行靜態分析,但需要時可視需要為每個呼叫的函式反序列化 AST。這就是 xtu-analyze.py 工具在頂層執行的功能,也就是透過 Ericsson 團隊編寫的精簡掃描版本scan-build-py/libscanbuild 目錄叫用工具。

步驟請見下方所述的 Fuchsia 包裝函式。完成後,系統會以 Apple plist 格式列出完整報表目錄,其中包含回報錯誤的詳細資料。

Ericsson 的包裝函式指令碼

執行跨翻譯單元分析的工具組合有兩種:

  • tools/xtu-build-new 底下的工具是頂層指令碼。由於基礎分析工具會失敗 (例如 CSA 當機),我已修補 Ericsson 的分支版本中的 xtu-analyze.py,以便將分析器的輸出內容 (stdout/stderr,而非報告) 轉儲到檔案。輸出內容會根據分析器的傳回代碼出現在 $OUT_DIR/{passes,fails},其中 $OUT_DIR 是傳遞至 xtu-analyze.py-o 引數的目錄。這些檔案特別實用的部分是開頭為 analyze: DEBUG: exec command in第二行,由 libscanbuild 工具 (下一個項目符號) 輸出。該指令在修改指令列經過長時間繁瑣的程序後,是實際叫用 CSA 的例子。因此,如果您要使用 gdb 對有問題的檔案執行 CSA,就必須編寫這個指令。
  • tools/scan-build-py 底下的工具是鳥的巢狀工具,可納入 Clang 的實際叫用。他們必須負責修改指令列。我並不太熟悉,過去也不會幹擾他們。

Fuchsia 包裝函式指令碼

這個非常小的殼層指令碼會包裝 Ericsson xtu-build-new 包裝函式。如要完整分析 Zircon,請務必先清除,並指定正確的 Clang 版本路徑。然後在 zircon 目錄中:

ninja -t clean && ninja && ./run.sh

如要僅建構核心,請將 TARGET 指定為環境變數:

ninja -t clean && ninja clean && TARGET=./build-zircon-pc-x64/zircon.elf ./run.sh

此外,指令碼也需要將 clangify.py 放在已設定可執行位元的 zircon 目錄中。分析完成後會有 .result-xtu 目錄,其中包含:

  • 一組 Apple plist 檔案,其中包含錯誤報告;
  • 失敗目錄,其中包含傳回非零的分析工具叫用 std{out,err} ;
  • 傳遞目錄,其中包含傳回 0 的分析工具叫用 std{out,err}。

查看分析結果

目前,剖析 plist 報表並透過網頁介面查看資料的唯一方法是使用在 Ericsson 開發的 CodeChecker 工具,用來理解程式碼及其他許多工作。CodeChecker 需要安裝大量依附元件。建議您使用 pipnpm 或其他任何依附元件來安裝,而不要使用 apt-get。簡單來說,在執行分析並將 plist 轉儲到 .result-xtu 後,您可以叫用 CodeChecker plist 來剖析 plist:

CodeChecker plist -d .result-xtu -n 2016-12-12T21:47_uniq_name -j 48

每次叫用 CodeChecker plist 時,-n 的引數都不得重複,因為代表單一剖析執行作業。此外,CodeChecker 也會抱怨。接著,執行 CodeChecker serverlocalhost:8001 上啟動網路伺服器,此伺服器會顯示先前所有剖析執行作業的報表。

取得協助

Samsung 修補程式組是由 Aleksei Sidorin 及其團隊所編寫。Aleksei 對 ASTImporter.cpp 和其他 AST 合併元素相當瞭解,而且很實用。他和 Sean Callanan 都很樂意檢查我的 AST 進口器修補程式。Aleksei 也在 2016 年的 LLVM 開發人員會議中,談論摘要型互通性分析相關講座

Ericsson 修補程式組是由 Gábor Horváth 和他的團隊所撰寫。Gábor 提供了一些建議,協助您瞭解如何使用 xtu-build-new 工具執行 CTU 分析。

我 (Kareem Khazem) 也很樂意提供相關協助。

LLVM irc 管道也很實用。

Zircon 專屬分析

上游 Clang 都很願意收到 Zircon 專用 Clang 檢查工具的修補程式。MutexInInterruptContext 檢查工具是其中之一 (從 Farid Molazem Tabrizi 寫入的 LLVM 傳遞),以及 SpinLockCheckerMutexChecker。Clang 調查的潛在審查人員包括 Devin Coughlin (來自 Apple)、Artem Dergachev (位於 Samsung 的 Aleksei Sidorin 團隊) 和 Anna Zaks (同樣在 Apple 工作)。

這些檢查工具通常可以「選擇加入」,也就是說,您必須先將標記傳送至分析器,才能啟用這類檢查工具:例如 -analyzer-checker=optin.zircon.MutexInInterruptContext

如果這些修補程式未出現在 Clang 中,請自行套用。如要運用這些函式與 Ericsson 包裝函式指令碼分析 Zircon,您必須在檔案結尾的 xtu-analyze.py 叫用中新增 -e optin.zircon.MutexInInterruptContext 選項,修改 Fuchsia 包裝函式指令碼MutexInInterruptContext 的修補程式包含測試套件,可做為分析功能的示例。

Zircon 中 CTU 支援的進度

AST 匯入工具中已修正的問題

絕大多數的 Zircon 檔案上,上游 CSA 都會當機。本節說明 Kareem Khazem 的一些問題及修正方法。

不支援的 AST 節點

Clang Static 分析工具無法匯入許多 Zircon 程式碼,因為不支援匯入特定種類的 AST 節點。以下列出支援這些節點的修補程式:

原子類型 已將修補程式合併至上游
CXXDependentScopeMemberExpr https://reviews.llvm.org/D26904
UnresolvedLookupExpr https://reviews.llvm.org/D27033
DependentSizedArray  
CXXUnresolvedConstructExpr  
UsingDecl https://reviews.llvm.org/D27181
UsingShadowDecl https://reviews.llvm.org/D27181
FunctionTemplateDecl https://reviews.llvm.org/D26904

一般來說,實作新節點類型的支援時,必須在 ASTImporter.cpp 中實作 VisitNode 函式,以及單元測試和功能測試;上述的修補程式含有範例。目前仍有許多不支援的 AST 節點;請 g 代表 error: cannot import unsupported AST node 的分析工具輸出目錄。

Ericsson 修補程式集只包含 Samsung 修補程式集中的部分 ASTImporter 程式碼。在某些情況下,您可以直接從 Samsung 修補程式集取得不支援的節點的 Visit 函式。不過,Samsung 修補程式集不含任何測試,因此在對該節點的上游支援之前,仍需編寫測試。

Segfaults 全球集數

ASTImporter.cpp 中的許多程式碼容易發生錯誤。有時候,Aleksei 都有私人修補程式 (例如這個問題),因此值得 (a-sid) 快速在 IRC 上快速查看。我的偵錯策略是檢查 second 字串的包裝函式輸出內容 (開頭為 analyze: DEBUG: exec command in,後方接上分析器的實際指令列),然後透過 gdb 執行該指令列。通常只需幾小時就能追蹤嚴重事件的來源。

CTU 前後發現的錯誤

VFS 中是否有可能的錯誤?

這是 oldparent 的雙重釋放,會在 system/ulib/fs/vfs.c:vfs_rename 上宣告為未初始化。後兩行之後,系統會使用 oldparent 呼叫 vfs_walk (相同檔案),做為第二個引數。只要進入 for 迴圈,然後在第一個迴圈上按下 return r 陳述式,即可從 vfs_walk 傳回,而無須指派給 oldparent。如果 r 的值大於零,我們就會前往 else if 陳述式,並在 oldparent 上呼叫 vn_release (該陳述式尚未初始化)。

執行緒可能發生錯誤?

此為「釋放後使用」的叢集。路徑如下:

  • kernel/kernel/thread.c:thread_detach_and_resume
    • 呼叫 thread_detach(t)
      • 傳回 thread_join(t, NULL, 0)
        • 免費 t,並於 NO_ERROR退回
      • 回程時間:NO_ERROR
    • 檢查錯誤為 1false1
    • 呼叫已釋出的 thread_resume(t)
      • 接著,thread_resume 就會存取 t 的欄位。

CTU 偽陽性

  • CSA 無法解析透過函式指標呼叫的函式實作。這表示它無法假設函式的傳回值可能,也無法假設函式可能會對輸出參數造成任何影響。
  • 分析器無法存取多個函式類別的實作。再次提醒,分析器無法得知這類函式輕觸輸出引數,因此會明確回報下列程式碼讀取垃圾值:

    struct timeval tv;
    gettimeofday(&tv, NULL);
    printf("%d\n", tv.tv_usec);   // [SPURIOUS REPORT] Access to
                                  // uninitialized variable ‘tv’
    
  • 以下列舉一些容易受到這種精確度類型的函式:

    • 系統呼叫 (例如 gettimeofday)
    • 編譯器內建項目 (例如 memcpy)