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-build 的新工具經過改良,與 Samsung 的 xtu-build 工具略有不同。這個修補程式集比 Samsung One 還新,作者已努力以 ToT 為基礎持續更新這個修補程式。

我們將使用 Ericsson 的修補程式集來修補 Clang,因為 AST 能夠徹底地合併工作,我們也取得了新的分析工具。但請注意,CTU 對 Zircon 的支援不完整。在某些情況下,Samsung 修補程式集即包含提供必要功能的程式碼 (詳情請見下方說明)。

建立支援 CTU 的 CSA 的步驟

  1. 照常下載及建構 Clang 和 LLVM。
  2. 在獨立目錄中,複製 Ericsson 的 Clang 分支,然後切換至 ctu-master 分支版本。
  3. 下載這個指令碼至 Ericsson 的分支中並執行。它應將一系列修補程式轉儲到修補程式目錄中。我刻意只從 Ericsson 改變初期,到 1bb3636 (這是我實習生的最新修訂版本) 發送修訂版本。

    • 如要取得 Ericsson 最新變更的內容,可以嘗試將指令碼中的 1bb3636 改成 HEAD。在指令碼中指定其他範圍,務必略過將上游修訂版本合併至 ctu-master 分支版本的修訂版本。git 記錄 -- 圖表可用於判斷上游修訂版本與 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 與。

執行 CTU 分析

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

CTU 的運作方式

首先,故事回溯:

非 CTU 靜態分析會分析每個 TU 的 AST;任何向外部函式發出的函式呼叫都會視為不透明。大致來說,CTU 分析是為了該函式實作的 AST,「替換」不透明函式呼叫節點。

因此,CTU 分析會照常開始分析 AST,但在遇到函式呼叫節點時,就會嘗試「合併」該函式的 AST。這會仰賴已事先將資料序列化到磁碟的 AST,好讓分析器可將 AST 重新載入至記憶體。此外,這也依賴於 AST 合併相關支援,這是 ASTImporter.cpp 的 Samsung 修補程式 (以及衍生自 Ericsson 的修補程式)。

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

總而言之,這次請快轉:

  • 使用 Clang 建構 Zircon,並將建構程序納入 bear 等程式中,以便記錄編譯器叫用並產生 JSON 編譯資料庫。
  • 重播相同的編譯步驟,但改為傾印 AST 檔案,而非物件檔案。這就是 xtu-build.py 工具的用途。
  • 照常執行靜態分析,但在需要時,將每個已呼叫函式的 AST 還原序列化。透過 Ericsson 團隊編寫的精簡掃描建構替換功能,叫用 scan-build-py/libscanbuild 目錄中的工具,即為 xtu-analyze.py 工具在頂層執行的作業。

相關步驟會在下文所述的 Fuchsia 包裝函式中記錄。產生報告後,系統會提供報告 (格式為 Apple plist) 的完整報表,其中包含已回報錯誤的詳細資料。

Ericsson 的包裝函式指令碼

有兩種工具可用於跨翻譯單位分析:

  • tools/xtu-build-new 底下的工具是頂層指令碼。由於基礎分析工具可能會失敗 (即因 CSA 異常終止),我已修補 xtu-analyze.py (在 Ericsson 的分支版本中),以便將分析工具 (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 server,在 localhost: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 (隸屬於 Aleksei Sidorin 的 Samsung 團隊) 和 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 節點匯入功能。下列為支援這些節點的修補程式:

AtomicType 修補程式已合併至上游
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 函式,以及單元測試和功能測試。上方 Kareem 的修補程式包含範例。仍有許多不支援的 AST 節點grep 的分析工具輸出目錄 error: cannot import unsupported AST node

Ericsson 修補程式集僅包含 Samsung 修補程式集中的 ASTImporter 程式碼子集。在某些情況下,對於不支援的節點,您可以直接從 Samsung 修補程式集取得 Visit 函式。不過,Samsung 修補程式集並未納入任何測試,因此在對該節點的支援之前,您還是需要先編寫測試。

Segfaults galore

ASTImporter.cpp 中的許多程式碼都發生錯誤。Aleksei 有時會針對問題 (例如這本手冊) 提供私人修補程式,因此建議您向 IRC 快速連線偵測 (a-sid) 通知。我的偵錯策略是針對開頭為 analyze: DEBUG: exec command in (接著是分析工具的實際指令列) 的第二個字串,查看包裝函式輸出,並透過 gdb 執行該指令列。通常只要幾小時的時間,即可追蹤分層來源。

CTU 之前和之後發現的錯誤

VFS 中可能存在的錯誤嗎?

這是 oldparent 的雙重釋放,會在 system/ulib/fs/vfs.c:vfs_rename 上宣告未初始化。兩行之後,系統會呼叫 vfs_walk (相同檔案) 做為第二個引數,並使用 oldparent。您可以從 vfs_walk 傳回 (不指派 oldparent),方法是輸入 for 迴圈,然後在第一個迴圈上點擊 return r 陳述式。如果 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)