点击左上角QiShare,关注我们
级别:★☆☆☆☆
标签:「编译过程」「GCC」「LLVM」「Clang」「词法分析」「语法分析」「中间代码」「目标文件」
作者: WYW审校:QiShare团队
前言
笔者前端时间在运行一个组内 Swift 项目的时候,发现编译时间比较长。所以查了部分优化项目编译时间的资料(当然还有部分原因是自己的电脑配置比较低)。并打算记录2篇文章。第一篇文章主要记录关于编译过程的内容,第二篇文章记录笔者在优化 Swift 项目编译时间的一点尝试。
本文是第一篇关于编译过程的文章。笔者将本文中介绍编译过程相关的内容,本文会按如下几个部分展开。
编译相关名词解释;
1.1 编译器
1.2 编译器架构
1.3 GCC
1.4 Clang
1.5 LLVM
编译过程简单了解;
2.1 词法分析;
2.2 语法分析;
2.3 语义分析;
2.4 生成中间代码;
2.5 优化中间代码;
2.6 生成目标代码;
用具体命令代码简单分析编译过程;首先名词解释部分,笔者会介绍编译器、GCC、LLVM相关内容。
编译器不是硬件,是可以把源程序编译为目标程序的计算机程序。
编译器(compiler)是一种计算机程序,它会将某种编程语言写成的源代码(原始语言)转换成另一种编程语言(目标语言)。引自维基百科编译器
编译器架构:
2.1 Frontend:前端
词法分析、语法分析、语义分析、生成中间代码(汇编指令)2.2 Optimizer:优化器
中间代码优化(汇编器进一步优化代码生成目标文件)2.3 Backend:后端
生成机器码(链接器把目标文件链接起来,生成可执行文件)
GCC 即 GNU 编译器套件(GNU Compiler Collection)是可以编译 C、Objective-C、C++等源程序的编译器。
GNU编译器套件(GNU Compiler Collection)包括C、C++、Objective-C、Fortran、Java、Ada和Go语言的前端,也包括了这些语言的库(如libstdc++、libgcj等等)。GCC的初衷是为GNU操作系统专门编写的一款编译器。GNU系统是彻底的自由软件。此处,"自由"的含义是它尊重用户的自由。引自360百科 GCC
Clang 是C、Objective-C、C++ 等语言的编译器前端。
Clang(发音为/ˈklæŋ/类似英文单字clang[1]) 是一个C、C++、Objective-C和Objective-C++编程语言的编译器前端。它采用了LLVM作为其后端,而且由LLVM2.6开始,一起发布新版本。它的目标是提供一个GNU编译器套装(GCC)的替代品,支持了GNU编译器大多数的编译设置以及非官方语言的扩展。引自维基百科Clang
LLVM(Low Level Virtual Machine 底层虚拟机),狭义的来说,LLVM 是 Clang 编译器的后端部分,用于把编译代码转化为目标文件。广义的来说 LLVM 是用来开发编译器前端和后端的组件和工具链。
LLVM是构架编译器(compiler)的框架系统,以C++编写而成,用于优化以任意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time),对开发者保持开放,并兼容已有脚本。
Low Level Virtual Machine 底层虚拟机。引自360百科LLVM
下图表明一个项目从源代码到可执行文件,会经历预处理、编译、装载、链接的过程。
下边笔者初步分享下 GCC 的编译过程。
这部分,笔者分2部分初步分享查看 GCC 编译源代码到可执行文件的过程,及 GCC 编译源代码到可执行文件的命令。
WYW:GCC wangyongwangyongwang$ GCC -ccc-print-phases main.c0: input, "main.c", c1: preprocessor, {0}, cpp-output2: compiler, {1}, ir3: backend, {2}, assembler4: assembler, {3}, object5: linker, {4}, image6: bind-arch, "x86_64", {5}, image
// 1: preprocessor 预编译WYW:GCCProcess wangyongwangyongwang$ gcc -E main.c -o main.i// 2: compiler生成汇编文件WYW:GCCProcess wangyongwangyongwang$ gcc -S main.i -o main.s// 3: backend、4: assembler生成目标文件WYW:GCCProcess wangyongwangyongwang$ gcc -c main.s -o main.o// 5: linker、6: bind-arch生成可执行文件WYW:GCCProcess wangyongwangyongwang$ gcc main.o -o main// 执行可执行文件WYW:GCCProcess wangyongwangyongwang$ sudo ./mainHello, World!
是计算机科学中将字符序列转换为标记(token)序列的过程。
根据给定的形式文法对由单词序列构成的输入文本进行分析并确定其语法结构。
检测程序的语义错误(如检查所需传入的参数类型)。
得到易于产生并易于翻译成目标程序的抽象机程序。
./可执行文件名字:执行当前路径的可执行文件名字
下边笔者将分享下 Clang 编译过程的内容,笔者目前的理解是 Clang 可以看做是 LLVM 的前端。LLVM 广义上来说是包含 Clang 的。
这部分,笔者分2部分初步分享查看 Clang 编译源代码到可以可执行文件的过程,及浅析 Clang 编译源代码到可执行文件的内容。
笔者先查看了自己电脑的 Clang 版本。
Apple LLVM version 11.0.0 (clang-1100.0.33.17)
WYW:~ wangyongwangyongwang$ objdump --versionApple LLVM version 11.0.0 (clang-1100.0.33.17) Optimized build. Default target: x86_64-apple-darwin19.2.0 Host CPU: broadwell Registered Targets: aarch64 - AArch64 (little endian) aarch64_be - AArch64 (big endian) arm - ARM arm64 - ARM64 (little endian) armeb - ARM (big endian) thumb - Thumb thumbeb - Thumb (big endian) x86 - 32-bit X86: Pentium-Pro and above x86-64 - 64-bit X86: EM64T and AMD64
WYW:LLVM wangyongwangyongwang$ clang -ccc-print-phases clang_main.c0: input, "clang_main.c", c1: preprocessor, {0}, cpp-output2: compiler, {1}, ir3: backend, {2}, assembler4: assembler, {3}, object5: linker, {4}, image6: bind-arch, "x86_64", {5}, image
笔者这里用的main.m 的文件内容如下:
// 一、预编译// 引入头文件预编译 注释的内容不会显示在预编译后的文件中#import #import // 条件编译#if __has_include() #import #ifndef Qi_HAS_IMPORT_UIKit #define QUC_HAS_IMPORT_UIKit 1 #endif#else #ifndef Qi_HAS_IMPORT_UIKit #define Qi_HAS_IMPORT_UIKit 0 #endif#endif// 宏预编译#define QiShareAge 2// 带参数的宏预编译#define QiShareMember(Member, Name) Member#Nameint main(int argc, char * argv[]) { // NSString * appDelegateClassName; @autoreleasepool { NSLog(@"Hello Clang Compile"); const char *me = QiShareMember("QiShare","WYW"); printf("member:%s \n", me); NSString *member = [NSString stringWithCString:me encoding:NSUTF8StringEncoding]; NSLog(@"member:%@", member); // Setup code that might create autoreleased objects goes here. // appDelegateClassName = NSStringFromClass([AppDelegate class]); /** 输出内容 * Hello Clang Compile * member:QiShare"WYW" * member:QiShare"WYW" */ } // return UIApplicationMain(argc, argv, nil, appDelegateClassName);}
// 预编译命令clang -E main.m -o main_precompile.i
1. #include #import 预编译指令,将被包含的文件插入到该预编译指令的位置2. 删除所有的注释// 及 块注释 但是会保留注释所在行为空白行3. 添加行号和文件标识4. 因为编译器有需要,会保留#pragma 编译指令
# 1 "main.m"# 1 "" 1# 1 "" 3# 374 "" 3# 1 "" 1# 1 "" 2# 1 "main.m" 2# 11 "main.m"# 1 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" 1 3typedef enum __attribute__((enum_extensibility(closed))) NSComparisonResult : NSInteger NSComparisonResult; enum NSComparisonResult : NSInteger { NSOrderedAscending &#61; -1L, NSOrderedSame, NSOrderedDescending};typedef NSComparisonResult (^NSComparator)(id obj1, id obj2);typedef enum __attribute__((flag_enum,enum_extensibility(open))) NSEnumerationOptions : NSUInteger NSEnumerationOptions; enum NSEnumerationOptions : NSUInteger { NSEnumerationConcurrent &#61; (1UL <<0), NSEnumerationReverse &#61; (1UL <<1),};# 1 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/Headers/FoundationLegacySwiftCompatibility.h" 1 3# 193 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" 2 3# 12 "main.m" 2# 31 "main.m"int main(int argc, char * argv[]) { &#64;autoreleasepool { NSLog(&#64;"Hello Clang Compile"); const char *me &#61; "QiShare""\"WYW\""; printf("member&#xff1a;%s \n", me); NSString *member &#61; [NSString stringWithCString:me encoding:NSUTF8StringEncoding]; NSLog(&#64;"member&#xff1a;%&#64;", member); }}
下边2张图左边是生成的预编译文件&#xff0c;右侧是 中的内容。可以发现有一一对应关系。
可以发现预编译 .m 文件生成的 .i 文件会把头文件导入&#xff0c;导入方式会具体的指明头文件在电脑上的具体问题&#xff1b;会去除注释&#xff0c;相应地会把注释换成空白行&#xff1b;添加行号。如main.m 的int main函数的在main.m 文件中位置是第31行。
clang -fmodules -E -Xclang -dump-tokens main.m
或clang -fmodules -E -Xclang -dump-tokens main.m > main_precompile.i
// 词法分析结果annot_module_include &#39;#import #import // 条件编译#if __has_include() #import #ifndef Qi_HAS_IMPORT_UIKit #define QUC_HAS_IMPORT_UIKit 1 #endif#else &#39; Loc&#61;<11:1>11:1>annot_module_include &#39;#import // 条件编译#if __has_include() #import #ifndef Qi_HAS_IMPORT_UIKit #define QUC_HAS_IMPORT_UIKit 1 #endif#else #ifndef Qi_HAS_IMPORT_UIKit #define Qi_HAS_IMPORT_UIKit 0&#39; Loc&#61;<12:1>12:1>int &#39;int&#39; [StartOfLine] Loc&#61;<31:1>31:1>identifier &#39;main&#39; [LeadingSpace] Loc&#61;<31:5>31:5>l_paren &#39;(&#39; Loc&#61;<31:9>31:9>int &#39;int&#39; Loc&#61;<31:10>31:10>identifier &#39;argc&#39; [LeadingSpace] Loc&#61;<31:14>31:14>comma &#39;,&#39; Loc&#61;<31:18>31:18>char &#39;char&#39; [LeadingSpace] Loc&#61;<31:20>31:20>star &#39;*&#39; [LeadingSpace] Loc&#61;<31:25>31:25>identifier &#39;argv&#39; [LeadingSpace] Loc&#61;<31:27>31:27>l_square &#39;[&#39; Loc&#61;<31:31>31:31>r_square &#39;]&#39; Loc&#61;<31:32>31:32>r_paren &#39;)&#39; Loc&#61;<31:33>31:33>l_brace &#39;{&#39; [LeadingSpace] Loc&#61;<31:35>31:35>at &#39;&#64;&#39; [StartOfLine] [LeadingSpace] Loc&#61;<34:6>34:6>identifier &#39;autoreleasepool&#39; Loc&#61;<34:7>34:7>l_brace &#39;{&#39; [LeadingSpace] Loc&#61;<34:23>34:23>identifier &#39;NSLog&#39; [StartOfLine] [LeadingSpace] Loc&#61;<36:10>36:10>l_paren &#39;(&#39; Loc&#61;<36:15>36:15>at &#39;&#64;&#39; Loc&#61;<36:16>36:16>string_literal &#39;"Hello Clang Compile"&#39; Loc&#61;<36:17>36:17>r_paren &#39;)&#39; Loc&#61;<36:38>36:38>semi &#39;;&#39; Loc&#61;<36:39>36:39>const &#39;const&#39; [StartOfLine] [LeadingSpace] Loc&#61;<38:10>38:10>char &#39;char&#39; [LeadingSpace] Loc&#61;<38:16>38:16>star &#39;*&#39; [LeadingSpace] Loc&#61;<38:21>38:21>identifier &#39;me&#39; Loc&#61;<38:22>38:22>equal &#39;&#61;&#39; [LeadingSpace] Loc&#61;<38:25>38:25>string_literal &#39;"QiShare"&#39; [LeadingSpace] Loc&#61;<38:27>>38:27>string_literal &#39;"\"WYW\""&#39; Loc&#61;<38:27 space>:3:1>>38:27>semi &#39;;&#39; Loc&#61;<38:57>38:57>identifier &#39;printf&#39; [StartOfLine] [LeadingSpace] Loc&#61;<39:10>39:10>l_paren &#39;(&#39; Loc&#61;<39:16>39:16>string_literal &#39;"member&#xff1a;%s \n"&#39; Loc&#61;<39:17>39:17>comma &#39;,&#39; Loc&#61;<39:33>39:33>identifier &#39;me&#39; [LeadingSpace] Loc&#61;<39:35>39:35>r_paren &#39;)&#39; Loc&#61;<39:37>39:37>semi &#39;;&#39; Loc&#61;<39:38>39:38>identifier &#39;NSString&#39; [StartOfLine] [LeadingSpace] Loc&#61;<40:10>40:10>star &#39;*&#39; [LeadingSpace] Loc&#61;<40:19>40:19>identifier &#39;member&#39; Loc&#61;<40:20>40:20>equal &#39;&#61;&#39; [LeadingSpace] Loc&#61;<40:27>40:27>l_square &#39;[&#39; [LeadingSpace] Loc&#61;<40:29>40:29>identifier &#39;NSString&#39; Loc&#61;<40:30>40:30>identifier &#39;stringWithCString&#39; [LeadingSpace] Loc&#61;<40:39>40:39>colon &#39;:&#39; Loc&#61;<40:56>40:56>identifier &#39;me&#39; Loc&#61;<40:57>40:57>identifier &#39;encoding&#39; [LeadingSpace] Loc&#61;<40:60>40:60>colon &#39;:&#39; Loc&#61;<40:68>40:68>identifier &#39;NSUTF8StringEncoding&#39; Loc&#61;<40:69>40:69>r_square &#39;]&#39; Loc&#61;<40:89>40:89>semi &#39;;&#39; Loc&#61;<40:90>40:90>identifier &#39;NSLog&#39; [StartOfLine] [LeadingSpace] Loc&#61;<41:10>41:10>l_paren &#39;(&#39; Loc&#61;<41:15>41:15>at &#39;&#64;&#39; Loc&#61;<41:16>41:16>string_literal &#39;"member&#xff1a;%&#64;"&#39; Loc&#61;<41:17>41:17>comma &#39;,&#39; Loc&#61;<41:30>41:30>identifier &#39;member&#39; [LeadingSpace] Loc&#61;<41:32>41:32>r_paren &#39;)&#39; Loc&#61;<41:38>41:38>semi &#39;;&#39; Loc&#61;<41:39>41:39>r_brace &#39;}&#39; [StartOfLine] [LeadingSpace] Loc&#61;<49:6>49:6>r_brace &#39;}&#39; [StartOfLine] Loc&#61;<51:1>51:1>eof &#39;&#39; Loc&#61;<51:2>51:2>
根据上述截图&#xff0c;可以发现词法分析的过程中&#xff0c;会把关键字和方法名描述符&#xff0c;逗号&#xff0c;分号、括号的行号和列号都会记录下来。
语法分析命令&#xff1a;clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
// WYW:clang wangyongwangyongwang$ clang -fmodules -fsyntax-only -Xclang -ast-dump main.m// 语法分析输出内容TranslationUnitDecl 0x7fbeb702da08 <> |-TypedefDecl 0x7fbeb702e2a0 <> implicit __int128_t &#39;__int128&#39;| &#96;-BuiltinType 0x7fbeb702dfa0 &#39;__int128&#39;|-TypedefDecl 0x7fbeb702e308 <> implicit __uint128_t &#39;unsigned __int128&#39;| &#96;-BuiltinType 0x7fbeb702dfc0 &#39;unsigned __int128&#39;|-TypedefDecl 0x7fbeb702e3a0 <> implicit SEL &#39;SEL *&#39;| &#96;-PointerType 0x7fbeb702e360 &#39;SEL *&#39; imported| &#96;-BuiltinType 0x7fbeb702e200 &#39;SEL&#39;|-TypedefDecl 0x7fbeb702e478 <> implicit id &#39;id&#39;| &#96;-ObjCObjectPointerType 0x7fbeb702e420 &#39;id&#39; imported| &#96;-ObjCObjectType 0x7fbeb702e3f0 &#39;id&#39; imported|-TypedefDecl 0x7fbeb702e558 <> implicit Class &#39;Class&#39;| &#96;-ObjCObjectPointerType 0x7fbeb702e500 &#39;Class&#39; imported| &#96;-ObjCObjectType 0x7fbeb702e4d0 &#39;Class&#39; imported|-ObjCInterfaceDecl 0x7fbeb702e5a8 <> implicit Protocol|-TypedefDecl 0x7fbeb702e8e8 <> implicit __NSConstantString &#39;struct __NSConstantString_tag&#39;| &#96;-RecordType 0x7fbeb702e700 &#39;struct __NSConstantString_tag&#39;| &#96;-Record 0x7fbeb702e670 &#39;__NSConstantString_tag&#39;|-TypedefDecl 0x7fbeb702e980 <> implicit __builtin_ms_va_list &#39;char *&#39;| &#96;-PointerType 0x7fbeb702e940 &#39;char *&#39;| &#96;-BuiltinType 0x7fbeb702daa0 &#39;char&#39;|-TypedefDecl 0x7fbeb7834868 <> implicit __builtin_va_list &#39;struct __va_list_tag [1]&#39;| &#96;-ConstantArrayType 0x7fbeb7834810 &#39;struct __va_list_tag [1]&#39; 1| &#96;-RecordType 0x7fbeb7834690 &#39;struct __va_list_tag&#39;| &#96;-Record 0x7fbeb7834600 &#39;__va_list_tag&#39;|-ImportDecl 0x7fbeb79ef2a8 <11:1> col:1 implicit Foundation11:1>|-ImportDecl 0x7fbeb79ef2e0 <12:1> col:1 implicit Foundation.NSObjCRuntime12:1>|-FunctionDecl 0x7fbeb79ef580 <31:1 line:51:1> line:31:5 main &#39;int (int, char **)&#39;31:1>| |-ParmVarDecl 0x7fbeb79ef330 <10 col:14> col:14 argc &#39;int&#39;10>| |-ParmVarDecl 0x7fbeb79ef440 <20 col:32> col:27 argv &#39;char **&#39;:&#39;char **&#39;20>| &#96;-CompoundStmt 0x7fbeb88158a8 <35 line:51:1>35>| &#96;-ObjCAutoreleasePoolStmt 0x7fbeb8815890 <34:6 line:49:6>34:6>| &#96;-CompoundStmt 0x7fbeb8815858 <34:23 line:49:6>34:23>| |-CallExpr 0x7fbeb7a000d0 <36:10 col:38> &#39;void&#39;36:10>| | |-ImplicitCastExpr 0x7fbeb7a000b8 <10> &#39;void (*)(id, ...)&#39; 10>| | | &#96;-DeclRefExpr 0x7fbeb79fffa8 <10> &#39;void (id, ...)&#39; Function 0x7fbeb79ef688 &#39;NSLog&#39; &#39;void (id, ...)&#39;10>| | &#96;-ImplicitCastExpr 0x7fbeb7a000f8 <16 col:17> &#39;id&#39;:&#39;id&#39; 16>| | &#96;-ObjCStringLiteral 0x7fbeb7a00038 <16 col:17> &#39;NSString *&#39;16>| | &#96;-StringLiteral 0x7fbeb7a00008 <17> &#39;char [20]&#39; lvalue "Hello Clang Compile"17>| |-DeclStmt 0x7fbeb7a005d0 <38:10 col:57>38:10>| | &#96;-VarDecl 0x7fbeb7a00128 <10 space>:3:1> main.m:38:22 used me &#39;const char *&#39; cinit10>| | &#96;-ImplicitCastExpr 0x7fbeb7a00208 <41 space>:3:1> &#39;const char *&#39; 41>| | &#96;-ImplicitCastExpr 0x7fbeb7a001f0 <38:41 space>:3:1> &#39;char *&#39; 38:41>| | &#96;-StringLiteral 0x7fbeb7a001c8 <38:41 space>:3:1> &#39;char [13]&#39; lvalue "QiShare\"WYW\""38:41>| |-CallExpr 0x7fbeb7a006f0 <39:10 col:37> &#39;int&#39;39:10>| | |-ImplicitCastExpr 0x7fbeb7a006d8 <10> &#39;int (*)(const char *, ...)&#39; 10>| | | &#96;-DeclRefExpr 0x7fbeb7a005e8 <10> &#39;int (const char *, ...)&#39; Function 0x7fbeb7a00228 &#39;printf&#39; &#39;int (const char *, ...)&#39;10>| | |-ImplicitCastExpr 0x7fbeb7a00738 <17> &#39;const char *&#39; 17>| | | &#96;-ImplicitCastExpr 0x7fbeb7a00720 <17> &#39;char *&#39; 17>| | | &#96;-StringLiteral 0x7fbeb7a00648 <17> &#39;char [14]&#39; lvalue "member\357\274\232%s \n"17>| | &#96;-ImplicitCastExpr 0x7fbeb7a00750 <35> &#39;const char *&#39; 35>| | &#96;-DeclRefExpr 0x7fbeb7a00670 <35> &#39;const char *&#39; lvalue Var 0x7fbeb7a00128 &#39;me&#39; &#39;const char *&#39;35>| |-DeclStmt 0x7fbeb88156c8 <40:10 col:90>40:10>| | &#96;-VarDecl 0x7fbeb7a00780 <10 col:89> col:20 used member &#39;NSString *&#39; cinit10>| | &#96;-ObjCMessageExpr 0x7fbeb8815688 <29 col:89> &#39;NSString * _Nullable&#39;:&#39;NSString *&#39; selector&#61;stringWithCString:encoding: class&#61;&#39;NSString&#39;29>| | |-ImplicitCastExpr 0x7fbeb8815670 <57> &#39;const char *&#39; 57>| | | &#96;-DeclRefExpr 0x7fbeb8815000 <57> &#39;const char *&#39; lvalue Var 0x7fbeb7a00128 &#39;me&#39; &#39;const char *&#39;57>| | &#96;-DeclRefExpr 0x7fbeb8815330 <69> &#39;NSStringEncoding&#39;:&#39;unsigned long&#39; EnumConstant 0x7fbeb8815028 &#39;NSUTF8StringEncoding&#39; &#39;NSStringEncoding&#39;:&#39;unsigned long&#39;69>| &#96;-CallExpr 0x7fbeb88157d0 <41:10 col:38> &#39;void&#39;41:10>| |-ImplicitCastExpr 0x7fbeb88157b8 <10> &#39;void (*)(id, ...)&#39; 10>| | &#96;-DeclRefExpr 0x7fbeb88156e0 <10> &#39;void (id, ...)&#39; Function 0x7fbeb79ef688 &#39;NSLog&#39; &#39;void (id, ...)&#39;10>| |-ImplicitCastExpr 0x7fbeb8815800 <16 col:17> &#39;id&#39;:&#39;id&#39; 16>| | &#96;-ObjCStringLiteral 0x7fbeb8815760 <16 col:17> &#39;NSString *&#39;16>| | &#96;-StringLiteral 0x7fbeb8815738 <17> &#39;char [12]&#39; lvalue "member\357\274\232%&#64;"17>| &#96;-ImplicitCastExpr 0x7fbeb8815818 <32> &#39;NSString *&#39; 32>| &#96;-DeclRefExpr 0x7fbeb8815780 <32> &#39;NSString *&#39; lvalue Var 0x7fbeb7a00780 &#39;member&#39; &#39;NSString *&#39;32>&#96;-
可以发现语法分析过程中会以语法树的结构说明待编译文件的目录&#xff0c;比如会说明main函数的开始结束行&#xff0c;会说明自动释放池参数、字符串变量、调用函数的行号等信息。
笔者查到语法分析器的用途有&#xff1a;
其实我们可以尝试制造语法错误。更具体地查看词法分析、语法分析部分的用处。
如果是出现如下错误&#xff0c;词法分析的时候并不会有问题&#xff0c;语法分析的时候会报错。
int int main(int argc, char * argv[]) {}
词法分析结果
&#39; Loc&#61;<12:1>12:1>int &#39;int&#39; [StartOfLine] Loc&#61;<31:1>31:1>int &#39;int&#39; [LeadingSpace] Loc&#61;<31:5>31:5>identifier &#39;main&#39; [LeadingSpace] Loc&#61;<31:9>31:9>l_paren &#39;(&#39; Loc&#61;<31:13>31:13>
语法分析结果
main.m:31:5: error: cannot combine with previous &#39;int&#39; declaration specifierint int main(int argc, char * argv[]) { ^TranslationUnitDecl 0x7ff14302c808 <> ...| &#96;-DeclRefExpr 0x7ff143922b80 <32> &#39;NSString *&#39; lvalue Var 0x7ff14391f780 &#39;member&#39; &#39;NSString *&#39;32>&#96;-1 error generated.
void logSomething(NSString *str1, NSString *str2) { NSLog(&#64;"str1&#xff1a;%&#64;--str2&#xff1a;%&#64;", str1, str2);}int main(int argc, char * argv[]) { // NSString * appDelegateClassName; &#64;autoreleasepool { logSomething(1, 2); }}
main.m:41:23: warning: incompatible integer to pointer conversion passing &#39;int&#39; to parameter of type &#39;NSString *&#39; [-Wint-conversion] logSomething(1, 2); ^main.m:31:29: note: passing argument to parameter &#39;str1&#39; herevoid logSomething(NSString *str1, NSString *str2) { ^main.m:41:26: warning: incompatible integer to pointer conversion passing &#39;int&#39; to parameter of type &#39;NSString *&#39; [-Wint-conversion] logSomething(1, 2); ^main.m:31:45: note: passing argument to parameter &#39;str2&#39; herevoid logSomething(NSString *str1, NSString *str2) { ^
可以发现像两个 int 关键字连续出现的语法错误&#xff0c;在词法分析的过程中并不会报错&#xff0c;在语法分析的时候&#xff0c;会有相应报错。
生成中间代码可用命令&#xff1a;clang -S -fobjc-arc -emit-llvm main.m -o main.ll
可以用 cat 命令查看中间代码内容。
下列代码是中间代码&#xff0c;中间代码中会有文件名和系统名的标识。中间代码中会有常量字符串&#xff0c;缓存、实例变量、消息列表、属性列表等信息说明。笔者不怎么了解中间代码&#xff0c;不做分析。
WYW:clang wangyongwangyongwang$ clang -S -fobjc-arc -emit-llvm main.m -o main.llWYW:clang wangyongwangyongwang$ cat main.ll; ModuleID &#61; &#39;main.m&#39;source_filename &#61; "main.m"target datalayout &#61; "e-m:o-i64:64-f80:128-n8:16:32:64-S128"target triple &#61; "x86_64-apple-macosx10.15.0"// 常量字符串&#xff0c;缓存、实例变量、消息列表、属性列表等信息%0 &#61; type opaque%struct.__NSConstantString_tag &#61; type { i32*, i32, i8*, i64 }%struct._class_t &#61; type { %struct._class_t*, %struct._class_t*, %struct._objc_cache*, i8* (i8*, i8*)**, %struct._class_ro_t* }%struct._objc_cache &#61; type opaque%struct._class_ro_t &#61; type { i32, i32, i32, i8*, i8*, %struct.__method_list_t*, %struct._objc_protocol_list*, %struct._ivar_list_t*, i8*, %struct._prop_list_t* }%struct.__method_list_t &#61; type { i32, i32, [0 x %struct._objc_method] }%struct._objc_method &#61; type { i8*, i8*, i8* }%struct._objc_protocol_list &#61; type { i64, [0 x %struct._protocol_t*] }%struct._protocol_t &#61; type { i8*, i8*, %struct._objc_protocol_list*, %struct.__method_list_t*, %struct.__method_list_t*, %struct.__method_list_t*, %struct.__method_list_t*, %struct._prop_list_t*, i32, i32, i8**, i8*, %struct._prop_list_t* }%struct._ivar_list_t &#61; type { i32, i32, [0 x %struct._ivar_t] }%struct._ivar_t &#61; type { i64*, i8*, i8*, i32, i32 }%struct._prop_list_t &#61; type { i32, i32, [0 x %struct._prop_t] }%struct._prop_t &#61; type { i8*, i8* }&#64;__CFConstantStringClassReference &#61; external global [0 x i32]&#64;.str &#61; private unnamed_addr constant [20 x i8] c"Hello Clang Compile\00", section "__TEXT,__cstring,cstring_literals", align 1&#64;_unnamed_cfstring_ &#61; private global %struct.__NSConstantString_tag { i32* getelementptr inbounds ([0 x i32], [0 x i32]* &#64;__CFConstantStringClassReference, i32 0, i32 0), i32 1992, i8* getelementptr inbounds ([20 x i8], [20 x i8]* &#64;.str, i32 0, i32 0), i64 19 }, section "__DATA,__cfstring", align 8&#64;.str.1 &#61; private unnamed_addr constant [13 x i8] c"QiShare\22WYW\22\00", align 1&#64;.str.2 &#61; private unnamed_addr constant [14 x i8] c"member\EF\BC\9A%s \0A\00", align 1; Function Attrs: nounwinddeclare i8* &#64;llvm.objc.autoreleasePoolPush() #1declare void &#64;NSLog(i8*, ...) #2declare i32 &#64;printf(i8*, ...) #2; Function Attrs: nonlazybinddeclare i8* &#64;objc_msgSend(i8*, i8*, ...) #3; Function Attrs: nounwinddeclare i8* &#64;llvm.objc.retainAutoreleasedReturnValue(i8*) #1; Function Attrs: nounwinddeclare void &#64;llvm.objc.storeStrong(i8**, i8*) #1; Function Attrs: nounwinddeclare void &#64;llvm.objc.autoreleasePoolPop(i8*) #1!0 &#61; !{i32 2, !"SDK Version", [2 x i32] [i32 10, i32 15]}!1 &#61; !{i32 1, !"Objective-C Version", i32 2}!2 &#61; !{i32 1, !"Objective-C Image Info Version", i32 0}!3 &#61; !{i32 1, !"Objective-C Image Info Section", !"__DATA,__objc_imageinfo,regular,no_dead_strip"}!4 &#61; !{i32 4, !"Objective-C Garbage Collection", i32 0}!5 &#61; !{i32 1, !"Objective-C Class Properties", i32 64}!6 &#61; !{i32 1, !"wchar_size", i32 4}!7 &#61; !{i32 7, !"PIC Level", i32 2}!8 &#61; !{!"Apple clang version 11.0.0 (clang-1100.0.33.17)"}!9 &#61; !{}
生成汇编文件&#xff1a;clang -S main.m -o main_compile.s
并通过cat main_compile.s 查看文件内容。
下述汇编文件是笔者参照网上资料做了部分注释后的内容。
// WYW:clang wangyongwangyongwang$ clang -S main.m -o main_compile.s// WYW:clang wangyongwangyongwang$ cat main_compile.s// 汇编指令// .section 指令指定接下来会执行哪一个段。 .section __TEXT,__text,regular,pure_instructions .build_version macos, 10, 15 sdk_version 10, 15// .globl 指令说明 _main 是一个外部符号。这就是我们的 main() 函数。这个函数对于二进制文件外部来说是可见的&#xff0c;因为系统要调用它来运行可执行文件。 .globl _main ## -- Begin function main// .align 指令指出了后面代码的对齐方式。在我们的代码中&#xff0c;后面的代码会按照 16(2^4) 字节对齐&#xff0c;如果需要的话&#xff0c;用 0x90 补齐。 .p2align 4, 0x90// main 函数的头部&#xff1a;_main: ## &#64;main// .cfi_startproc 指令通常用于函数的开始处。CFI 是调用帧信息 (Call Frame Information) 的缩写。这个调用 帧 以松散的方式对应着一个函数。它与后面的 .cfi_endproc 相匹配&#xff0c;以此标记出 main() 函数结束的地方。 .cfi_startproc## %bb.0:// 在 OS X上&#xff0c;我们会有 X86_64 的代码&#xff0c;对于这种架构&#xff0c;有一个东西叫做 ABI ( 应用二进制接口 application binary interface)&#xff0c;ABI 指定了函数调用是如何在汇编代码层面上工作的。在函数调用期间&#xff0c;ABI 会让 rbp 寄存器 (基础指针寄存器 base pointer register) 被保护起来。当函数调用返回时&#xff0c;确保 rbp 寄存器的值跟之前一样&#xff0c;这是属于 main 函数的职责。pushq %rbp 将 rbp 的值 push 到栈中&#xff0c;以便我们以后将其 pop 出来。 pushq %rbp// 接下来是两个 CFI 指令&#xff1a;这将会输出一些关于生成调用堆栈展开和调试的信息。我们改变了堆栈和基础指针&#xff0c;而这两个指令可以告诉编译器它们都在哪儿&#xff0c;或者更确切的&#xff0c;它们可以确保之后调试器要使用这些信息时&#xff0c;能找到对应的东西。 .cfi_def_cfa_offset 16 .cfi_offset %rbp, -16// 接下来&#xff0c;movq %rsp, %rbp 将把局部变量放置到栈上。subq $32, %rsp 将栈指针移动 32 个字节&#xff0c;也就是函数会调用的位置。我们先将老的栈指针存储到 rbp 中&#xff0c;然后将此作为我们局部变量的基址&#xff0c;接着我们更新堆栈指针到我们将会使用的位置。 movq %rsp, %rbp .cfi_def_cfa_register %rbp subq $64, %rsp movl %edi, -4(%rbp) movq %rsi, -16(%rbp)// 调用 autoreleasePoolPush callq _objc_autoreleasePoolPush leaq L__unnamed_cfstring_(%rip), %rsi movq %rsi, %rdi movq %rax, -40(%rbp) ## 8-byte Spill movb $0, %al// 调用 NSLog callq _NSLog leaq L_.str.1(%rip), %rsi movq %rsi, -24(%rbp) movq -24(%rbp), %rsi leaq L_.str.2(%rip), %rdi movb $0, %al// 调用 printf callq _printf movq L_OBJC_CLASSLIST_REFERENCES_$_(%rip), %rsi movq -24(%rbp), %rdx movq L_OBJC_SELECTOR_REFERENCES_(%rip), %rdi movq %rdi, -48(%rbp) ## 8-byte Spill movq %rsi, %rdi movq -48(%rbp), %rsi ## 8-byte Reload movl $4, %ecx movl %eax, -52(%rbp) ## 4-byte Spill callq *_objc_msgSend&#64;GOTPCREL(%rip) leaq L__unnamed_cfstring_.4(%rip), %rcx movq %rax, -32(%rbp) movq -32(%rbp), %rsi movq %rcx, %rdi movb $0, %al callq _NSLog movq -40(%rbp), %rdi ## 8-byte Reload callq _objc_autoreleasePoolPop xorl %eax, %eax addq $64, %rsp popq %rbp retq// main() 函数结束的地方 .cfi_endproc ## -- End function .section __TEXT,__cstring,cstring_literalsL_.str: ## &#64;.str .asciz "Hello Clang Compile"
生成目标文件的命令为 clang -fmodules -c main.m -o main_obj.o
。可以通过 objdump
查看目标文件中的内容。objdump -t main_obj.o
或 objdump -syms main_obj.o
用于查看目标文件符号表入口。更多 objdump 命令可查看 14. objdump 二进制文件分析
// clang -fmodules -c main.m -o main_obj.o// WYW:clang wangyongwangyongwang$ objdump -t main_obj.omain_obj.o: file format Mach-O 64-bit x86-64SYMBOL TABLE:0000000000000140 l O __TEXT,__ustring l_.str.30000000000000000 g F __TEXT,__text _main0000000000000000 *UND* _NSLog0000000000000000 *UND* _OBJC_CLASS_$_NSString0000000000000000 *UND* ___CFConstantStringClassReference0000000000000000 *UND* _objc_autoreleasePoolPop0000000000000000 *UND* _objc_autoreleasePoolPush0000000000000000 *UND* _objc_msgSend0000000000000000 *UND* _printf
下列大部分内容引自&#xff1a;Mach-O 可执行文件
一个可执行文件包含多个段(section)。可执行文件不同的部分将加载进不同的 section&#xff0c;并且每个 section 会转换进某个 segment 里。这个概念对于所有的可执行文件都是成立的。
我们来看看
a.out
二进制中的 section。我们可以使用size
工具来观察
xcrun size -x -l -m main_obj.o
WYW:clang wangyongwangyongwang$ xcrun size -x -l -m main_obj.oSegment : 0x1c0 (vmaddr 0x0 fileoff 1488) Section (__TEXT, __text): 0x9b (addr 0x0 offset 1488) Section (__TEXT, __cstring): 0x2f (addr 0x9b offset 1643) Section (__DATA, __cfstring): 0x40 (addr 0xd0 offset 1696) Section (__DATA, __objc_classrefs): 0x8 (addr 0x110 offset 1760) Section (__TEXT, __objc_methname): 0x1c (addr 0x118 offset 1768) Section (__DATA, __objc_selrefs): 0x8 (addr 0x138 offset 1800) Section (__TEXT, __ustring): 0x14 (addr 0x140 offset 1808) Section (__DATA, __objc_imageinfo): 0x8 (addr 0x154 offset 1828) Section (__LD, __compact_unwind): 0x20 (addr 0x160 offset 1840) Section (__TEXT, __eh_frame): 0x40 (addr 0x180 offset 1872) total 0x1b2total 0x1c0
__TEXT
segment 包含了被执行的代码。它被以只读和可执行的方式映射。进程被允许执行这些代码&#xff0c;但是不能修改。这些代码也不能对自己做出修改&#xff0c;因此这些被映射的页从来不会被改变。
__DATA
segment 以可读写和不可执行的方式映射。它包含了将会被更改的数据。
otool(1)
来观察一个 section 中的内容&#xff1a;
xcrun otool -s __TEXT __text main_obj.o
WYW:clang wangyongwangyongwang$ xcrun otool -s __TEXT __text main_obj.omain_obj.o:Contents of (__TEXT,__text) section0000000000000000 55 48 89 e5 48 83 ec 40 89 7d fc 48 89 75 f0 e8 0000000000000010 00 00 00 00 48 8d 35 b5 00 00 00 48 89 f7 48 89 0000000000000020 45 d8 b0 00 e8 00 00 00 00 48 8d 35 7f 00 00 00 0000000000000030 48 89 75 e8 48 8b 75 e8 48 8d 3d 7d 00 00 00 b0 0000000000000040 00 e8 00 00 00 00 48 8b 35 c3 00 00 00 48 8b 55 0000000000000050 e8 48 8b 3d e0 00 00 00 48 89 7d d0 48 89 f7 48 0000000000000060 8b 75 d0 b9 04 00 00 00 89 45 cc ff 15 00 00 00 0000000000000070 00 48 8d 0d 78 00 00 00 48 89 45 e0 48 8b 75 e0 0000000000000080 48 89 cf b0 00 e8 00 00 00 00 48 8b 7d d8 e8 00 0000000000000090 00 00 00 31 c0 48 83 c4 40 5d c3
xcrun otool -v -t main_obj.o&#xff0c;通过添加
-v
来查看反汇编代码&#xff1a;
WYW:clang wangyongwangyongwang$ xcrun otool -v -t main_obj.omain_obj.o:(__TEXT,__text) section_main:0000000000000000 pushq %rbp0000000000000001 movq %rsp, %rbp0000000000000004 subq $0x40, %rsp0000000000000008 movl %edi, -0x4(%rbp)000000000000000b movq %rsi, -0x10(%rbp)000000000000000f callq 0x140000000000000014 leaq 0xb5(%rip), %rsi000000000000001b movq %rsi, %rdi000000000000001e movq %rax, -0x28(%rbp)0000000000000022 movb $0x0, %al0000000000000024 callq 0x290000000000000029 leaq 0x7f(%rip), %rsi0000000000000030 movq %rsi, -0x18(%rbp)0000000000000034 movq -0x18(%rbp), %rsi0000000000000038 leaq 0x7d(%rip), %rdi000000000000003f movb $0x0, %al0000000000000041 callq 0x460000000000000046 movq 0xc3(%rip), %rsi000000000000004d movq -0x18(%rbp), %rdx0000000000000051 movq 0xe0(%rip), %rdi0000000000000058 movq %rdi, -0x30(%rbp)000000000000005c movq %rsi, %rdi000000000000005f movq -0x30(%rbp), %rsi0000000000000063 movl $0x4, %ecx0000000000000068 movl %eax, -0x34(%rbp)000000000000006b callq *(%rip)0000000000000071 leaq 0x78(%rip), %rcx0000000000000078 movq %rax, -0x20(%rbp)000000000000007c movq -0x20(%rbp), %rsi0000000000000080 movq %rcx, %rdi0000000000000083 movb $0x0, %al0000000000000085 callq 0x8a000000000000008a movq -0x28(%rbp), %rdi000000000000008e callq 0x930000000000000093 xorl %eax, %eax0000000000000095 addq $0x40, %rsp0000000000000099 popq %rbp000000000000009a retq
otool(1)
可用于查看一个 section 中的内容&#xff1a;
WYW:clang wangyongwangyongwang$ xcrun otool -s __TEXT __cstring main_obj.omain_obj.o:Contents of (__TEXT,__cstring) section000000000000009b 48 65 6c 6c 6f 20 43 6c 61 6e 67 20 43 6f 6d 70 00000000000000ab 69 6c 65 00 51 69 53 68 61 72 65 22 57 59 57 22 00000000000000bb 00 6d 65 6d 62 65 72 ef bc 9a 25 73 20 0a 00
下边我们可以反汇编目标文件&#xff0c;来查看逆向反汇编后得到的汇编文件&#xff0c;和之前正向用于生成目标文件的汇编文件是否一致。下图表明&#xff0c;反汇编的汇编文件&#xff0c;和之前正向生成目标文件的汇编文件的内容基本一样。只是反汇编文件中去除了部分命令。
// 生成执行文件WYW:clang wangyongwangyongwang$ clang main_obj.o -o mainExec// 执行可执行文件WYW:clang wangyongwangyongwang$ ./mainExec2020-04-01 22:12:15.766 mainExec[84398:2934623] Hello Clang Compilemember&#xff1a;QiShare"WYW" 2020-04-01 22:12:15.767 mainExec[84398:2934623] member&#xff1a;QiShare"WYW"
了解更多iOS及相关新技术&#xff0c;请关注我们的公众号&#xff1a;
更多精彩内容&#xff0c;尽在