本文将简单介绍 Clang LLVM 相关的知识,然后介绍一下代码是如何一步步的编译运行的,以及可以利用 clang 能做些什么。
简介
编译器就是语言翻译器,把高级语言翻译成计算机能够执行的机器语言。
语言翻译主要工作流程:
源代码 (source code) → 预处理器 (preprocessor) → 编译器 (compiler) → 目标代码 (object code) → 链接器 (linker) → 可执行程序 (executables)
LLVM (Low Level Virtual Machine) 是一个开源的编译器架构。Clang 是 LLVM 的一个编译器前端,它目前支持 C, C++, Objective-C 等编程语言。
Clang 对源程序进行预处理、词法分析、语法分析,并将分析结果转换为 Abstract Syntax Tree ( 抽象语法树 ) ,最后使用 LLVM 作为编译器后端代码的生成器。
Clang 的开发目标是提供一个可以替代 GCC 的前端编译器。Apple 对 Objective-C 新增很多特性,想做的很多功能,比如更好的IDE支持,GCC 不能很好的支持,于是,苹果开发了 Clang 与 LLVM 来完全取代GCC。Clang作为编译器前端,LLVM作为编译器后端。
与 GCC 相比,Clang 是一个重新设计的编译器前端,具有一系列优点,例如模块化,代码简单易懂,占用内存小以及容易扩展和重用等。由于 Clang 在设计上的优异性,使得 Clang 非常适合用于设计源代码级别的分析和转化工具。Clang 也已经被应用到一些重要的开发领域,如 Static Analysis 是一个基于 Clang 的静态代码分析工具。
用 Clang 编译 OC 程序
当用 Xcode 创建了项目,然后点击 run 运行的时候,可以在 Xcode 中看到编译的信息:
看一下编译 main.m 文件的信息,相当于执行了一长串的命令,其中命令的参数就是你在 Build Settings 里面设置的一些选项,拼接成了这一串命令,主要的就是调用 Clang 编译的命令:
clang -x objective-c -fobjc-arc ... main.m -o main.o
clang 命令:
在命令行中 clang 相当于一个黑盒的驱动,里面封装了编译管线、前段命令、LLVM 命令、Toolchain 命令等。
clang 编译的过程
下面通过编译 main.m 文件,来看一下编译的过程。main.m 中是很简单的打印代码:
#import <Foundation/Foundation.h>
int main() {
@autoreleasepool {
NSLog(@"Hello world!");
}
return 0;
}
命令行输入命令:
clang -fobjc-arc -framework Foundation main.m -o main
-fobjc-arc 表示编译器需要支持 ARC 特性
-framework Foundation 表示引用Foundation框架
上面的命令会生成可执行文件 main,然后命令行输入执行文件 main:
./main
看到运行结果:
Hello world!
实质上,上述编译过程是分为四个阶段进行的,即预处理(Preprocess)、编译(Compilation)、汇编 (Assembly)和连接(Linking)。
1、预处理(Preprocess)
这个步骤会进行,import 头文件的处理,宏定义的展开,#开头的预处理指令等,的处理。
预处理的命令:
clang -E main.m 或者 clang -E main.m -o test.i
查看文件可看到头部,十几万行的预处理
-fmodules 参数可以把那些库打包导入,import Foundation,这样每次编译都不用展开那么多东西
clang -E -fmodules main.m
2、词法分析(Lexical Analysis)
词法分析,将预处理过的代码转化成一个个的 Token,对应的命令为:
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
处理后的结果为:
可看到代码,拆分成了一个个的 Token
3、语法分析(Semantic Analysis)
语法分析,在 clang 中由 Parser 和 Sema 两个模块配合完成,来验证语法是否正确。
提示代码哪里写错了,少了冒号、括号等一些提示。
根据当前语言的语法,生成语义节点,并将所有节点组合成抽象语法树(AST: Abstract Syntax Tree),对应的命令为:
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
处理后,生成的语法树:
FunctionDecl,表示函数名 main
CompoundStmt,表示大括号 {}
ObjCAutoreleasePoolStmt,表示 @autoreleasepool
静态分析:
编译器把源码生成了抽象语法树,编译器可以对这棵树做分析处理,以找出代码中的错误,比如类型检查:即检查程序中是否有类型错误。例如:如果代码中给某个对象发送了一个消息,编译器会检查这个对象是否实现了这个消息(函数、方法)。此外,clang 对整个程序还做了其它更高级的一些分析,以确保程序没有错误。
还有一种类型检查:动态分析
动态的在运行时做检查,静态的在编译时做检查。编写代码时可以向任意对象发送任何消息,在运行时,才会检查对象是否能够响应这些消息。
4、IR 代码生成 (CodeGen)
clang 完成代码的标记,解析和分析后,接着就会生成 LLVM 代码。将上一步的语法树从顶至下遍历,翻译成 LLVM IR。
LLVM IR 是编译前端的输出,也是 LLVM 后端的输入,是前后端桥接语言。
与 OC Runtime 桥接:
-
Class、Meta Class、Protocol、Category 内存结构生成,并存放在指定 section 中(如 Class:_DATA, _objc_classrefs)
-
Method、Ivar、Property 内存结构生成
-
组成 method_list、ivar_list、property_list 并填入 Class
-
Non-Fragile ABI:为每个 Ivar 合成 OBJC_IVAR_$_ 偏移值常量
-
存取 Ivar 的语句(_ivar = 123; int a = _ivar;)
转写成 base + OBJC_IVAR_$_的形式 -
将语法树中的 ObjCMessageExpr 翻译成相应版本的 obj_msgSend,对 super 关键字的调用翻译成 objc_msgSendSuper
-
根据修饰符 strong、weak、copy、atomic 合成 @property 自动实现的 setter、getter
-
处理 @synthesize
-
生成 block_layout 的数据结构
-
变量的 capture(__block __weak)
-
生成 _block_invoke 函数
-
ARC 分析对象引用关系,将 obj_storeStrong/objc_storeWeak 等 ARC 代码插入
-
将 ObjcCAutoreleasePoolStmt 转译成 objc_autoreleasePoolPush/Pop
-
实现自动调用 [super dealloc]
-
为每个拥有 ivar 的 Class 合成 .cxx_destructor 方法来自动释放类的成员变量,代替 MRC 时代的 self.xxx = nil
可以使用下面的命令,查看生成 IR 代码:
clang -S -fobjc-arc -emit-llvm main.m -o main.ll
-S 编译到汇编层面
-fobjc-arc 开启ARC
-emit-llvm 生成中间的LLVM语言
执行命令后可得到文件 main.ll:
define i32 @main() #0 {
%1 = alloca i32, align 4
store i32 0, i32* %1, align 4
%2 = call i8* @llvm.objc.autoreleasePoolPush() #1
notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*))
call void @llvm.objc.autoreleasePoolPop(i8* %2)
ret i32 0
}
这里 LLVM 会去做些优化工作,在 Xcode 的编译设置里也可以设置优化级别 -O1,-O3,-Os :
clang -O3 -S -fobjc-arc -emit-llvm main.m -o main.ll
下面是优化后的中间LLVM代码
define i32 @main() local_unnamed_addr #0 {
%1 = tail call i8* @llvm.objc.autoreleasePoolPush() #1
notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*)), !clang.arc.no_objc_arc_exceptions !9
tail call void @llvm.objc.autoreleasePoolPop(i8* %1) #1
ret i32 0
}
生成字节码 LLVM Bitcode
clang -emit-llvm -c main.m -o main.bc
将上面生成的 IR 代码生成二进制码格式
5、生成 Target 相关汇编(Assemble)
生成对应机器相关的汇编语言:
clang -S -fobjc-arc main.m -o main.s
生成 Target 相关 Mach-O 文件
clang -fmodules -c main.m -o main.o
Mach-O(Mach object)文件,是一种用于记录可执行文件、对象代码、共享库、动态加载代码和内存转储的文件格式,是macOS/iOS上程序以及库的标准格式。
6、生成可执行文件
链接生成可执行文件 main。
将程序的目标文件与所需的所有附加的目标文件(静态连接库和动态连接库)连接起来,最终生成可执行文件。
clang main.o -o main
执行就可以看到程序运行
./main
7、编译过程小结
- 预处理
- 符号化 (Tokenization)
- 宏定义的展开
- #include 的展开
- 语法和语义分析
- 将符号化后的内容转化为一棵解析树 (parse tree)
- 解析树做语义分析
- 输出一棵抽象语法树(Abstract Syntax Tree* (AST))
- 生成代码和优化
- 将 AST 转换为更低级的中间码 (LLVM IR)
- 对生成的中间码做优化
- 生成特定目标代码
- 输出汇编代码
- 汇编器
- 将汇编代码转换为目标对象文件。
- 链接器
- 将多个目标对象文件合并为一个可执行文件 (或者一个动态库)
clang 的应用
上面介绍了,用 clang 编译的过程,从预处理到词法分析、语法分析,再到生成汇编语言。
那么我们能用 clang 做些什么?
libclang
libclang 是一个 C 的类库,提供了简洁的 API,可以对 C 和 clang 做桥接,并可以用它对所有的源码做分析处理,如获取 Tokens、遍历语法树、代码补全、获取诊断信息等。
libclang api 稳定,不受 clang 源码更新影响,但是只能访问上层语法树,不能获取全部信息。
推荐使用 ClangKit 它是基于 clang 提供的功能,用 Objective-C 进行封装的一个库。
clang 还提供了一个直接使用 LibTooling 的 C++ 类库。它能够发挥 clang 的强大功能,可以对源码做任意类型的分析,甚至重写程序,对语法树有完全的控制权。如果你想要给 clang 添加一些自定义的分析、创建自己的重构器 (refactorer)、或者需要基于现有代码做出大量修改,甚至想要基于工程生成相关图形或者文档,那么可以使用 LibTooling。
将 OC 代码转换为 C/C++
当需要查看 OC 代码底层实现时,可以利用 clang 将 OC 代码转换为 C/C++ 代码。
下面命令可以进行转换:
clang -rewrite-objc main.m
//-fobjc-arc 表示ARC环境。-fobjc-runtime 表示当前运行时环境。
clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.15 main.m
执行后,可在当前文件夹下生成 main.cpp 文件。
当 .m 文件包含系统头文件时,会报错找不到头文件,可以指定SDK解决:
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m
或者
xcrun -sdk iphonesimulator13.2 clang -rewrite-objc main.m
//其中的 sdk iphonesimulator13.2 可以通过命令 xcodebuild -showsdks 来查看
或者使用下面命令,指定了 SDK 和架构,转换后的代码会少一些:
xcrun -sdk iphoneos clang -arch arm64 -w -rewrite-objc main.m
References
https://llvm.org/
http://clang.llvm.org/docs/index.html
https://objccn.io/issue-6-2/
https://objccn.io/issue-6-3/
https://www.cnblogs.com/wfwenchao/p/5543595.html
https://www.ibm.com/developerworks/cn/opensource/os-cn-clang/index.html
网友评论