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 需要安裝大量依附元件,建議您使用 pip、npm 或任何替代方法 (而非使用 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 票證上轉移),以及 SpinLockChecker 和 MutexChecker。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)