天天看點

體驗Clang對C語言的編譯體驗Clang對C語言的編譯

體驗Clang對C語言的編譯

目錄

  • 體驗Clang對C語言的編譯
    • 0. Clang指令簡介
    • 1. 詞法分析(Lexical Analysis)
    • 2. 文法分析(Semantic Analysis)
    • 3. 中間代碼生成(Intermediate Representation)
      • 3.1 文本形式的中間代碼生成
      • 3.2 二進制形式的中間代碼生成
    • 4. 優化(Optimizer)
    • 5. 使用Clang直接生成機器碼(Machine Code)
    • 6. 心得體會
    • 7. 參考文獻

使用Clang來分析如下的簡單C語言的函數,檔案名為 larger_number.c ( 這算不上是一個程式,隻能算是一個函數子產品)

// larger_number.c
int larger(int a, int b) {
  if(a <= b) 
    return 	b;
  else 
    return a;
}
           
在本文中,我們體驗一下Clang的如下功能:
  • 詞法分析(Lexical Analysis)
  • 文法分析(Semantic Analysis)
  • 中間代碼生成(Intermediate Representation)
  • 優化(Optimizer)
  • 使用Clang直接生成機器碼(Machine Code)

0. Clang指令簡介

這裡簡單列出下面指令中可能出現的參數,到時可以作為一個參考

  • 想看清clang的全部過程,可以先通過

    -E

    檢視

    clang

    在預編譯處理這步做了什麼。
    clang -E larger_number.c
  • 允許

    modules

    的語言特性
    -fmodules
  • clang

    編譯器傳遞參數
    -Xclang
  • 運作預處理器,拆分内部代碼段為各種

    token

    -dump-tokens
  • 防止編譯器生成代碼,隻是文法級别的說明和修改
    -fsyntax-only
  • 建構抽象文法樹AST,然後對其進行拆解和調試
    -ast-dump
  • 隻運作預處理和編譯步驟
    -S
  • 使用

    LLVM

    描述彙編和對象檔案
    -emit-llvm
  • 隻運作預處理,編譯和彙編步驟
    -c

1. 詞法分析(Lexical Analysis)

詞法分析;簡單講,如果将程式了解成一個個的句子,那麼詞法分析的作用就是将句子中的每個單詞拆分出來 ,而程式中的單詞,我們有一個專門的名字:token

## 0. 打開Windows PowerShell

## 1. 首先,把larger_number.c放在demos目錄下
PS F:\_llvm\llvm-project\build\Release\demos> ls
-a----       10/27/2019  11:37 PM            104 larger_number.c

## 2. 使用clang詞法分析指令
PS F:\_llvm\llvm-project\build\Release\demos> clang -fmodules -E -Xclang -dump-tokens larger_number.c
int 'int'        [StartOfLine]  Loc=<larger_number.c:2:1>
identifier 'larger'      [LeadingSpace] Loc=<larger_number.c:2:5>
l_paren '('             Loc=<larger_number.c:2:11>
int 'int'               Loc=<larger_number.c:2:12>
identifier 'a'   [LeadingSpace] Loc=<larger_number.c:2:16>
comma ','               Loc=<larger_number.c:2:17>
int 'int'        [LeadingSpace] Loc=<larger_number.c:2:19>
identifier 'b'   [LeadingSpace] Loc=<larger_number.c:2:23>
r_paren ')'             Loc=<larger_number.c:2:24>
l_brace '{'      [LeadingSpace] Loc=<larger_number.c:2:26>
if 'if'  [StartOfLine] [LeadingSpace]   Loc=<larger_number.c:3:3>
l_paren '('             Loc=<larger_number.c:3:5>
identifier 'a'          Loc=<larger_number.c:3:6>
lessequal '<='   [LeadingSpace] Loc=<larger_number.c:3:8>
identifier 'b'   [LeadingSpace] Loc=<larger_number.c:3:11>
r_paren ')'             Loc=<larger_number.c:3:12>
return 'return'  [StartOfLine] [LeadingSpace]   Loc=<larger_number.c:4:5>
identifier 'b'   [LeadingSpace] Loc=<larger_number.c:4:13>
semi ';'                Loc=<larger_number.c:4:14>
else 'else'      [StartOfLine] [LeadingSpace]   Loc=<larger_number.c:5:3>
return 'return'  [StartOfLine] [LeadingSpace]   Loc=<larger_number.c:6:5>
identifier 'a'   [LeadingSpace] Loc=<larger_number.c:6:12>
semi ';'                Loc=<larger_number.c:6:13>
r_brace '}'      [StartOfLine]  Loc=<larger_number.c:7:1>
eof ''          Loc=<larger_number.c:7:2>
           
體驗Clang對C語言的編譯體驗Clang對C語言的編譯

這裡我們可以簡單地分析一下這個 dump-tokens 的輸出;

首先分析一下 輸出的結果中每行開頭和結尾的意思:

int ‘int’ … <larger_number.c:2:1>

identifier ‘larger’ … <larger_number.c:2:5>

很顯然,我們通過檢視larger_number.c源檔案,可以知道,

每行開頭表示的是分析出來的token單詞

,而結尾的尖括号(<>)裡面的内容是此

token所在的檔案名以及在檔案中的行号和列号

2. 文法分析(Semantic Analysis)

文法分析容易了解,就是把整個檔案内的代碼内容生成一顆

抽象文法樹(AST,Abstract Syntax Tree)

這裡不過多分析AST,等到後續學習到相關的AST分析的時候再說

PS F:\_llvm\llvm-project\build\Release\demos> clang -fmodules -fsyntax-only -Xclang -ast-dump larger_number.c
TranslationUnitDecl 0x225e0dc4e38 <<invalid sloc>> <invalid sloc>
|-TypedefDecl 0x225e0dc56d0 <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128'
| `-BuiltinType 0x225e0dc53d0 '__int128'
|-TypedefDecl 0x225e0dc5740 <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128'
| `-BuiltinType 0x225e0dc53f0 'unsigned __int128'
|-TypedefDecl 0x225e0dc5a78 <<invalid sloc>> <invalid sloc> implicit __NSConstantString 'struct __NSConstantString_tag'
| `-RecordType 0x225e0dc5830 'struct __NSConstantString_tag'
|   `-Record 0x225e0dc5798 '__NSConstantString_tag'
|-TypedefDecl 0x225e0dc5ae8 <<invalid sloc>> <invalid sloc> implicit size_t 'unsigned long long'
| `-BuiltinType 0x225e0dc5010 'unsigned long long'
|-TypedefDecl 0x225e0dc5b80 <<invalid sloc>> <invalid sloc> implicit __builtin_ms_va_list 'char *'
| `-PointerType 0x225e0dc5b40 'char *'
|   `-BuiltinType 0x225e0dc4ed0 'char'
|-TypedefDecl 0x225e0dc5bf0 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list 'char *'
| `-PointerType 0x225e0dc5b40 'char *'
|   `-BuiltinType 0x225e0dc4ed0 'char'
`-FunctionDecl 0x225e0ff8180 <larger_number.c:2:1, line:7:1> line:2:5 larger 'int (int, int)'
  |-ParmVarDecl 0x225e0dc5c60 <col:12, col:16> col:16 used a 'int'
  |-ParmVarDecl 0x225e0dc5ce0 <col:19, col:23> col:23 used b 'int'
  `-CompoundStmt 0x225e0ff83d8 <col:26, line:7:1>
    `-IfStmt 0x225e0ff83b0 <line:3:3, line:6:12> has_else
      |-BinaryOperator 0x225e0ff8300 <line:3:6, col:11> 'int' '<='
      | |-ImplicitCastExpr 0x225e0ff82d0 <col:6> 'int' <LValueToRValue>
      | | `-DeclRefExpr 0x225e0ff8290 <col:6> 'int' lvalue ParmVar 0x225e0dc5c60 'a' 'int'
      | `-ImplicitCastExpr 0x225e0ff82e8 <col:11> 'int' <LValueToRValue>
      |   `-DeclRefExpr 0x225e0ff82b0 <col:11> 'int' lvalue ParmVar 0x225e0dc5ce0 'b' 'int'
      |-ReturnStmt 0x225e0ff8358 <line:4:5, col:13>
      | `-ImplicitCastExpr 0x225e0ff8340 <col:13> 'int' <LValueToRValue>
      |   `-DeclRefExpr 0x225e0ff8320 <col:13> 'int' lvalue ParmVar 0x225e0dc5ce0 'b' 'int'
      `-ReturnStmt 0x225e0ff83a0 <line:6:5, col:12>
        `-ImplicitCastExpr 0x225e0ff8388 <col:12> 'int' <LValueToRValue>
          `-DeclRefExpr 0x225e0ff8368 <col:12> 'int' lvalue ParmVar 0x225e0dc5c60 'a' 'int'
           

下面是截圖的有顔色效果,可能好看一些:

體驗Clang對C語言的編譯體驗Clang對C語言的編譯

3. 中間代碼生成(Intermediate Representation)

這部分是個人最感興趣的地方

Clang生成的中間代碼有

三種形式

可選

  • 文本:類似彙編語言,便于閱讀,擴充名.ll
  • 記憶體:記憶體格式
  • 二進制:占用空間小,擴充名.bc

3.1 文本形式的中間代碼生成

clang -S -emit-llvm larger_number.c	
           

可以看到,目前目錄下多了一個 .ll檔案:

體驗Clang對C語言的編譯體驗Clang對C語言的編譯
體驗Clang對C語言的編譯體驗Clang對C語言的編譯

通過查找資料,我找到了兩個比較可靠的參考資料,通過這兩個資料,我 完成了這個IR代碼的手動反編譯工作:

  • 1. 《Getting Started with LLVM Core Libraries》 (Chapter 5. The LLVM Intermediate Representation # 5.3 Introducing the LLVM IR language syntax )
  • 2. llvm參考手冊

下面是手工反編譯後IR後的分析結果:

體驗Clang對C語言的編譯體驗Clang對C語言的編譯

3.2 二進制形式的中間代碼生成

clang -c -emit-llvm larger_number.c
           
體驗Clang對C語言的編譯體驗Clang對C語言的編譯

4. 優化(Optimizer)

clang -S -O3 -emit-llvm larger_number.c
           

可以看到,新生成的 .ll檔案,其代碼量少得多。這裡的

O3是優化級别

體驗Clang對C語言的編譯體驗Clang對C語言的編譯

5. 使用Clang直接生成機器碼(Machine Code)

我們可以直接用下面介紹的指令,來

直接編譯上面的函數代碼,發現會報錯,大意是沒有找到函數的主入口;

很顯然,

一個完整可運作的C語言程式,必須要有main函數作為程式的入口。

是以我們需要編寫一個完整的C語言程式;我們将larger_number.c改寫成如下的完整代碼:

// larger_number.c

// 如果不加這句,編譯時會提示讓我們使用scanf_s, printf_s
#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h> 
#include <stdlib.h>

int larger(int a, int b) {
	if (a <= b)
		return 	b;
	else
		return a;
}

int main() {
	int a, b;
	printf("Input a b : \n");
	scanf("%d%d", &a, &b);
	printf("The larger number is %d\n", larger(a, b));

	system("pause");
	return 0;
}
           

現在我們試一下,Clang的

直接生成可執行檔案

的指令:

## 生成中間代碼
PS F:\_llvm\llvm-project\build\Release\demos> clang larger_number.c -o larger_number.exe
## 檢視是否生成了
PS F:\_llvm\llvm-project\build\Release\demos> ls
-a----       10/28/2019  12:01 AM           2324 larger_number.bc
-a----       10/28/2019  12:22 AM            347 larger_number.c
-a----       10/28/2019  12:22 AM         153088 larger_number.exe
-a----       10/28/2019  12:03 AM           1165 larger_number.ll
## 直接運作程式,發現能夠成功地運作
PS F:\_llvm\llvm-project\build\Release\demos> .\larger_number.exe
Input a b :
3 4
The larger number is 4
Press any key to continue . . .
           
體驗Clang對C語言的編譯體驗Clang對C語言的編譯

6. 心得體會

  • 仍然有許多的東西不是很懂,希望在後續的學習中不斷深入
  • 怎樣使用Clang的庫,讓我們能夠利用到Clang生成的詞法tokens,文法樹,這是我後續需要學習的知識;
  • 期待後續的知識的學習:
    • Clang與LLVM基本庫,子產品
    • 編寫自己的分析程式
  • 對IR的反編譯最感興趣
  • 發現很多的部落格都引用了一本書:《Getting Started With LLVM Core Libraries》 ,後續希望可以參考這本書來學習更多的知識。
  • 官方參考手冊也很有用,可以非常友善地 查閱IR 的文法與解釋:llvm參考手冊

7. 參考文獻

LLVM實戰入門PPT

《Getting Started with LLVM Core Libraries》 (Chapter 5. The LLVM Intermediate Representation # 5.3 Introducing the LLVM IR language syntax )

llvm參考手冊

clang常用文法介紹