体验Clang对C语言的编译

体验Clang对C语言的编译


使用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>

在这里插入图片描述
这里我们可以简单地分析一下这个 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'

下面是截图的有颜色效果,可能好看一些:
在这里插入图片描述

3. 中间代码生成(Intermediate Representation)

这部分是个人最感兴趣的地方
Clang生成的中间代码有三种形式可选

  • 文本:类似汇编语言,便于阅读,扩展名.ll
  • 内存:内存格式
  • 二进制:占用空间小,扩展名.bc

3.1 文本形式的中间代码生成

clang -S -emit-llvm larger_number.c	

可以看到,当前目录下多了一个 .ll文件
在这里插入图片描述在这里插入图片描述
通过查找资料,我找到了两个比较可靠的参考资料,通过这两个资料,我 完成了这个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后的分析结果:
在这里插入图片描述

3.2 二进制形式的中间代码生成

clang -c -emit-llvm larger_number.c

在这里插入图片描述

4. 优化(Optimizer)

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

可以看到,新生成的 .ll文件,其代码量少得多。这里的O3是优化级别
在这里插入图片描述

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 . . .

在这里插入图片描述

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常用语法介绍

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章