天天看點

一文帶你梳理Clang編譯步驟及指令

摘要: 本文簡單介紹了Clang編譯過程中涉及到的步驟和每個步驟的産物,并簡單分析了部分影響預處理和編譯成功的部分因素。

本文分享自華為雲社群《Clang編譯步驟及指令梳理》,作者:maijun。

本文簡單介紹部分Clang和LLVM的編譯指令。更關注前端部分(生成 IR 部分)。

我們可以使用指令列印出來Clang支援的步驟,如下:

根據上面的介紹,可以根據每一部分的結果,分為5個步驟(不包含上面的第0步):preprocessor、compiler、backend、assembler、linker等。

具體到 Clang 中每一步驟生成的結果檔案。我們可以使用下面的示意圖來表示:

一文帶你梳理Clang編譯步驟及指令

說明:上面的示意圖以Clang編譯一個C檔案為例,介紹了Clang編譯過程中涉及到的中間檔案類型:

(1) test.c 為輸入的源碼(對應步驟 0);

(2) test.i 為預處理檔案(對應步驟 1 的輸出,cpp-output 中,cpp 不是指 C++ 語言,而是 c preprocessor 的 縮寫);

(3) test.bc 為 bitcode檔案,是clang的一種中間表示(對應步驟 2 的輸出);

(4) test.ll 為一種文本化的中間表示,可以打開來看的(對應步驟 2 的輸出, 和 .bc 一樣都是中間表示,可以互相轉化);

(5) test.s 為彙編結果(對應步驟 3 的輸出);

(6) test.o 為單檔案生成的二進制檔案(對應步驟 4 的輸出);

(7) image 為可執行檔案(對應步驟 5 的輸出)。

注意:示意圖畫的也并不完整,如下介紹:

(1) 箭頭所指的方向,表示可以從一種類型的檔案,生成箭頭所指的檔案類型;

(2) 圖中箭頭并沒有畫完,比如可以從 test.c 生成 test.s, test.o 等。如果将上面的示意圖當做一種 有向圖,那麼基于 箭頭 所指的方向,隻要 節點能連接配接的點,都是可以做轉換的;

(3) 圖中的實線和虛線,隻是表示本人關心的Clang編譯器中的内容,并沒有其他的含義,本文也隻介紹圖中實線部分的内容,虛線部分的内容不做介紹。

下面介紹部分涉及到上面步驟的轉換指令:

上面列出了一部分Clang不同檔案直接轉換的指令(和第 1 部分的 示意圖 序号比對,還是隻關心前端部分)。隻是最後增加了一個将多個 bc 合并為一個 bc file 的指令。

我們可以通過如下的指令檢視源碼的AST結構:

列印出來的AST資訊,其實是預處理之後展開的源碼資訊,源碼的AST内容在列印出來的内容的最下面。

如下面的代碼:

列印出來的部分AST(僅根目前檔案内容比對部分)如下:

一文帶你梳理Clang編譯步驟及指令

頭上的頭檔案引用等已經展開,沒有了,但是下面的 main 函數定義,則如上面的 FunctionDecl 所示,并且給出了 代碼中的位置。這裡就不詳細分析AST的結構了,寫幾個例子比對一下就很容易了解。

目前,很多靜态代碼分析工具,都采用 Clang 和 LLVM 作為底座來開發靜态代碼分析工具。Clang自己也有 clang-tidy 工具可以用來做 C/C++ 語言的靜态代碼分析。為了能夠用 Clang 和 LLVM 來成功分析 C/C++ 代碼,需要考慮如何成功使用 Clang 和 LLVM 來編譯 C/C++ 代碼。可以考慮的是,成功生成 bc file,是靜态代碼分析的基礎操作。

預處理過程,作用跟名字一樣,都可以不當做編譯的一個步驟,而是編譯的一個預處理操作。我們說得再直白一點兒,其實就是做了一個文本替換的活兒,就是對 C/C++ 代碼中的 預處理指令 進行處理。預處理指令很簡單,比如 #include,#define 等,都是預處理指令(可以參考:https://docs.microsoft.com/en-us/cpp/preprocessor/preprocessor-directives?view=msvc-170,或者google下,很多介紹的)。

如果程式中沒有預處理指令,即使我們随便瞎寫的代碼,預處理也一般不會有問題,如下的代碼(main.c):

我們仍然可以正确得到 預處理結果:

為了成功執行預處理執行,很容易了解,就是可以對程式中的所有的 預處理指令 進行處理。比如:

(1) #include,依賴了一個頭檔案,我們能不能成功找到這個頭檔案;

(2) #define,定義了一個宏,在程式中定義宏的時候,我們能不能準确找到宏(找到,還必須準确);

(3) 其他指令。

這一步是針對上一步生成的預處理指令,進行解析的操作。這一步才是最關鍵的,歸根結底,我們需要保證一點:使Clang編譯器可以正确識别出來代碼中内容表示的文法結構,并且接納這種文法結構!

舉一些簡單例子:

(1) -std 用來指定支援的 C/C++ 标準的,如果我們沒有指定,那麼就會采用 Clang 預設的标準來編譯,就可能導緻文法不相容;

(2) -Werror=* 等參數,可能将某些能識别的文法,給搞成錯誤的使用;

(3) 其他的部分,跟文法識别的參數;

(4) 還有一部分的文法,可能 Clang 自始至終就沒有進行适配,這種就要考慮修改源碼了。

在真正編譯中,如果連結有問題,那就會失敗,但是在靜态代碼分析中,連結有失敗(無法連結)或者錯誤(不相關的給連結在一起),可能多點兒分析誤報或者漏報,一般不會導緻分析失敗。這類問題,影響的不是中間表示的生成,而是分析結果(影響跨檔案的過程間分析,影響對built-in函數的模組化等)。

一般,連結指令的捕獲,target資訊配置等,會影響這部分的能力。當然,也跟你實作的工具有關(如果實作的工具,就沒有跨檔案的能力,這部分内容也沒啥影響)。

點選關注,第一時間了解華為雲新鮮技術~

繼續閱讀