iOS 编译与链接一:编译的过程
一:编译器
编译器是什么已不用多说,一句话从代码到机器码就是编译器的工作.
左边输入源码,右边输出机器码
Frontend表示前端,主要负责词法分析、语法分析、语义分析、生成中间代码.这时就会进行各种检查,会报错或者警告.
Optimizer表示优化器,负责中间代码的优化,去除冗余代码,优化结构
Backend表示后端,生成机器码,并且进行链接,也就是将不同的二进制文件合并成一个可执行文件.
1.LLVM
Xcode5之后完全使用LLVM作为编译器.
LLVM也是上面说的那种Frontend -> Optimizer ->Backend架构.
不过LLVM路子很野,可以有很多个接口,也就是前端(Frontend),每一种前端对应一种或多种语言,这些前端最终都会生成相同的中间代码,叫做LLVM IR;
优化器的从始至终只处理LLVM IR.
新增一个前端不需要对LLVM的优化器进行调整,只需要新增一个前端; 增加一个新的平台只需要增加一个后端即可.
相比较而言,GCC就支持一个新的前端或者后端就要麻烦的多,原本的GCC家族(C,C++,OC),以及Java、.NET、Python、Ruby等都可以使用LLVM编译.
LLVM IR有3种表示形式,
存在内存中.
存在硬盘中的.ll代码文件,可以阅读.
存在硬盘中的二进制文件,扩展名是.bc,也就是bitcode.
2.Clang
Clang就是一个LLVM前端,负责将C,C++,OC翻译成LLVM IR.
Clang的工作内容:
预处理, 去掉注释,头文件的引用关系,把宏定义对应到各个位置
静态分析,给出错误信息,警告信息和修复方案
词法分析,这里会把代码切成一个个 Token,括号,符号,关键字等等都被切割出来
语法分析,验证语法是否正确,将所有节点组成抽象语法树AST
生成 LLVM IR, CodeGen会负责将语法树自顶向下遍历逐步翻译成 LLVM IR
3.Swift
同样LLVM中还需要一个前端负责对 Swift 源代码进行静态分析和纠错,并转换为 LLVM IR,这个前端也叫swift.
不过swift比clang的过程要复杂一些,多了一个生成SIL的过程.
Swift的工作内容:
导入Clang模块并将它们导出的OC API 映射到相应的 Swift API
解析生成AST
生成SIL,将经过类型检查的 AST 降级为 SIL
优化SIL,为程序执行额外的高级 Swift 特定优化,包括自动引用计数优化,虚拟化和通用专业化
将 SIL 降级到 LLVM IR
二.编译流程
引用戴铭老师的例子走一遍,原文
1.编译一个main.m
装了Xcode就自带LLVM,可以直接试一试.
创建一个项目,覆盖main.m的代码
#import
#define DEFINEEight 8
#pragma 这是标记
//这是注释
int main(){
@autoreleasepool {
int eight = DEFINEEight;
int six = 6;
NSString* site = [[NSString alloc] initWithUTF8String:"starming"];
int rank = eight + six;
NSLog(@"%@ rank %d", site, rank);
}
return 0;
}
运行:
clang -ccc-print-phases main.m
输出:
+- 0: input, "main.m", objective-c
+- 1: preprocessor, {0}, objective-c-cpp-output
+- 2: compiler, {1}, ir
+- 3: backend, {2}, assembler
+- 4: assembler, {3}, object
+- 5: linker, {4}, image
6: bind-arch, "arm64", {5}, image
第0步引入文件
第1步预编译,输出c++文件
第2步编译为LLVM IR文件
第3步输出汇编文件
第4步输出二进制文件
第5步链接各二进制文件
第6步根据架构输出对应可执行文件
执行:
clang -E main.m
输出了非常多的东西,因为导入了foundation,先看最后几行
# 1 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/Headers/FoundationLegacySwiftCompatibility.h" 1 3
# 187 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" 2 3
# 22 "main.m" 2
#pragma 这是标记
int main(){
@autoreleasepool {
int eight = 8;
int six = 6;
NSString* site = [[NSString alloc] initWithUTF8String:"starming"];
int rank = eight + six;
NSLog(@"%@ rank %d", site, rank);
}
return 0;
}
除了foundation,还可以看到DEFINEEight被替换成了8,注释没了,但是#pragma还在
所以预编译就做了这些事:导入文件,去除注释,替换宏定义.
这一步会生成main.cpp文件,在main.m的同一路径.几万行代码,在最后可以找到替换为C++的main函数
int main(){
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int eight = 8;
int six = 6;
NSString* site = ((NSString * _Nullable (*)(id, SEL, const char * _Nonnull))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("initWithUTF8String:"), (const char *)"starming");
int rank = eight + six;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_681pp9bd3c31j1_m0jqnt4h00000gn_T_main_3eda9c_mi_0, site, rank);
}
return 0;
}
接下来是词法分析
执行:
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
输出
annot_module_include '#import
#define DEFINEEight 8
#pragma 这是标记
//这是注释
int main(){
@autoreleasepool {
int eight = DEFINEEight;
int six = 6;
NSString* site = [[NSString alloc] initWithUTF8String:"starming"];
int rank = eight + six;
NSLog(@"%@ rank %d", site, rank);
}
return 0;
}
@???W?/?V?A?`' Loc=
int 'int' [StartOfLine] Loc=
identifier 'main' [LeadingSpace] Loc=
l_paren '(' Loc=
r_paren ')' Loc=
l_brace '{' Loc=
at '@' [StartOfLine] [LeadingSpace] Loc=
identifier 'autoreleasepool' Loc=
l_brace '{' [LeadingSpace] Loc=
int 'int' [StartOfLine] [LeadingSpace] Loc=
identifier 'eight' [LeadingSpace] Loc=
equal '=' [LeadingSpace] Loc=
numeric_constant '8' [LeadingSpace] Loc=>
semi ';' Loc=
int 'int' [StartOfLine] [LeadingSpace] Loc=
identifier 'six' [LeadingSpace] Loc=
equal '=' [LeadingSpace] Loc=
numeric_constant '6' [LeadingSpace] Loc=
semi ';' Loc=
identifier 'NSString' [StartOfLine] [LeadingSpace] Loc=
star '*' Loc=
identifier 'site' [LeadingSpace] Loc=
equal '=' [LeadingSpace] Loc=
l_square '[' [LeadingSpace] Loc=
l_square '[' Loc=
identifier 'NSString' Loc=
identifier 'alloc' [LeadingSpace] Loc=
r_square ']' Loc=
identifier 'initWithUTF8String' [LeadingSpace] Loc=
colon ':' Loc=
string_literal '"starming"' Loc=
r_square ']' Loc=
semi ';' Loc=
int 'int' [StartOfLine] [LeadingSpace] Loc=
identifier 'rank' [LeadingSpace] Loc=
equal '=' [LeadingSpace] Loc=
identifier 'eight' [LeadingSpace] Loc=
plus '+' [LeadingSpace] Loc=
identifier 'six' [LeadingSpace] Loc=
semi ';' Loc=
identifier 'NSLog' [StartOfLine] [LeadingSpace] Loc=
l_paren '(' Loc=
at '@' Loc=
string_literal '"%@ rank %d"' Loc=
comma ',' Loc=
identifier 'site' [LeadingSpace] Loc=
comma ',' Loc=
identifier 'rank' [LeadingSpace] Loc=
r_paren ')' Loc=
semi ';' Loc=
r_brace '}' [StartOfLine] [LeadingSpace] Loc=
return 'return' [StartOfLine] [LeadingSpace] Loc=
numeric_constant '0' [LeadingSpace] Loc=
semi ';' Loc=
r_brace '}' [StartOfLine] Loc=
eof '' Loc=
可以看出词法分析只需要处理main.m中的代码,把所有的字符串,符号,括号都拆分开,拆出来的每一个部分,叫做token.
在接下来是语法分析
执行:
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
输出
-FunctionDecl 0x15ab12390 line:26:5 main 'int ()'
`-CompoundStmt 0x15b026d48
|-ObjCAutoreleasePoolStmt 0x15b026d00
| `-CompoundStmt 0x15b026cc8
| |-DeclStmt 0x15ab12530
| | `-VarDecl 0x15ab124a8 line:28:13 used eight 'int' cinit
| | `-IntegerLiteral 0x15ab12510 'int' 8
| |-DeclStmt 0x15ab4e6f8
| | `-VarDecl 0x15ab12560 col:13 used six 'int' cinit
| | `-IntegerLiteral 0x15ab125c8 'int' 6
| |-DeclStmt 0x15b024be8
| | `-VarDecl 0x15ab4e750 col:19 used site 'NSString *' cinit
| | `-ObjCMessageExpr 0x15ab50b90 'NSString * _Nullable':'NSString *' selector=initWithUTF8String:
| | |-ObjCMessageExpr 0x15ab4eb58 'NSString *' selector=alloc class='NSString'
| | `-ImplicitCastExpr 0x15ab50b78 'const char * _Nonnull':'const char *'
| | `-ImplicitCastExpr 0x15ab50b60 'char *'
| | `-StringLiteral 0x15ab4ebc8 'char [9]' lvalue "starming"
| |-DeclStmt 0x15b0252a8
| | `-VarDecl 0x15b024c18 col:13 used rank 'int' cinit
| | `-BinaryOperator 0x15b024d20 'int' '+'
| | |-ImplicitCastExpr 0x15b024cf0 'int'
| | | `-DeclRefExpr 0x15b024c80 'int' lvalue Var 0x15ab124a8 'eight' 'int'
| | `-ImplicitCastExpr 0x15b024d08 'int'
| | `-DeclRefExpr 0x15b024cb8 'int' lvalue Var 0x15ab12560 'six' 'int'
| `-CallExpr 0x15b026c48 'void'
| |-ImplicitCastExpr 0x15b026c30 'void (*)(id, ...)'
| | `-DeclRefExpr 0x15b0252c0 'void (id, ...)' Function 0x15b024d48 'NSLog' 'void (id, ...)'
| |-ImplicitCastExpr 0x15b026c80 'id':'id'
| | `-ObjCStringLiteral 0x15b025340 'NSString *'
| | `-StringLiteral 0x15b025318 'char [11]' lvalue "%@ rank %d"
| |-ImplicitCastExpr 0x15b026c98 'NSString *'
| | `-DeclRefExpr 0x15b025360 'NSString *' lvalue Var 0x15ab4e750 'site' 'NSString *'
| `-ImplicitCastExpr 0x15b026cb0 'int'
| `-DeclRefExpr 0x15b025398 'int' lvalue Var 0x15b024c18 'rank' 'int'
`-ReturnStmt 0x15b026d38
`-IntegerLiteral 0x15b026d18 'int' 0
这一步会检查语法的正确性,给出警告,报错,以及修改建议.生成的内容叫做抽象语法树AST.
生成AST之后就可以开始生成IR代码了
执行:
clang -O3 -S -fobjc-arc -emit-llvm main.m -o main.ll
输出main.ll文件,路径和main.m相同,是可读的.
这里"-O3"是LLVM的优化策略,有-O1,-O3,-Os,也可以不设置.
如果设置了bitcode,还可以进一步优化.
接下来生成汇编
clang -S -fobjc-arc main.m -o main.s
生成目标文件
clang -fmodules -c main.m -o main.o
生成可执行文件并执行
clang main.o -o main
./main
输出
starming rank 14
2.从Xcode观察编译过程
这里的过程可以在buildsetting,Build Phases 和 Build Rules中进行配置
首先是预编译,可以看到new build system等内容.
然后是编译cocoapods的targets,包括创建framework(cocoapods使用use framework),copy头文件,以及编译.m文件
接下来是主target,其实是与cocoapods的targes一致的,这一步主target也会被打包成framework.
也是拷贝.h文件,编译swift文件,编译.m文件,编译xib文件,拷贝资源文件,
接下来执行cocoapods脚本,Build Phases的脚本.
最后拷贝swift标准库以及签名.
每一个都可以点开详情.
比如编译.m文件可以看到clang信息,这些基本是可以在build setting中进行配置的.
前面是CompileC任务描述
然后是切换路径
最后clang -x objective-c -target x86_64-apple-ios12.0-simulator ...就是编译的命令
- 编译的流程
1.处理文件信息
2.执行CocoaPod编译前脚本
3.编译.m文件(h文件是不需要编译的),执行clang命令
4.链接framework
5.拷贝和编译xib,bundle文件
6.编译 ImageAssets
7.处理 info.plist
8.执行CocoaPod脚本
9.拷贝Swift标准库
10.创建.app文件和签名
3.配置编译选项
-
Build settings设置在build的过程中各个阶段的选项,clang的配置就属于这个范围.
-
Build Phases构建可执行文件的规则,指定 target 的依赖项目,指定在target build之前需要先build的依赖.
在Compile Source中指定必须编译的文件,这些文件同样会根据Build Setting和Build Rules里的设置来处理.
在Link Binary With Libraries里会列出所有的静态库和动态库,它们会和编译生成的目标文件链接.
把静态资源拷贝到bundle里.
另外还可以通过在build phases里添加自定义脚本来做些事情,比如像CocoaPods所做的那样. -
Build Rules指定不同文件类型如何编译,每条build rule指定了该类型如何处理以及输出在哪,可以增加新规则对特定文件类型添加处理方法.
上面这些都是在Xcode UI中可视化的,这些信息最终需要以文件的格式保存下来,那就是.pbxproj文件,
路径在[项目名称].xcodeproj包里的project.pbxproj.
打开这个文件,在最后一行有一个
rootObject = F7036F002511EC050031CE83 /* Project object */;
搜索这个rootObject ID,可以找到PBXProject section,
这个文件就是以section为单位描述配置.
/* Begin PBXProject section */
F7036F002511EC050031CE83 /* Project object */ = {
isa = PBXProject;
attributes = {
CLASSPREFIX = XX;
LastUpgradeCheck = 1310;
ORGANIZATIONNAME = XXXX;
TargetAttributes = {
F7036F072511EC050031CE83 = {
CreatedOnToolsVersion = 11.6;
LastSwiftMigration = 1240;
};
};
};
...
这PBXProject section里找到target
targets = (
F7036F072511EC050031CE83 /* XXXXX */,
);
再搜索这个ID,就可以找到更多配置,这个.pbxproj文件就是以这种id索引的方式进行记录和查找.
比如继续顺着这个ID,可以找到更多的定义,可以看到buildPhases,buildConfiguration.
再顺着找可以看到cocoapods, copy resource等等定义.
/* Begin PBXNativeTarget section */
F7036F072511EC050031CE83 /* XXXXX */ = {
isa = PBXNativeTarget;
buildConfigurationList = F7036F212511EC080031CE83 /* Build configuration list for PBXNativeTarget "XXXXX" */;
buildPhases = (
2E9C3A1138A1AED934114EBC /* [CP] Check Pods Manifest.lock */,
F7036F042511EC050031CE83 /* Sources */,
F7036F052511EC050031CE83 /* Frameworks */,
F7036F062511EC050031CE83 /* Resources */,
34DB7EF940E3C9401AD2798F /* [CP] Embed Pods Frameworks */,
25DB3E569FEFB4DC91CF364D /* [CP] Copy Pods Resources */,
);
...
版权声明:
作者:zhangchen
链接:https://www.techfm.club/p/49245.html
来源:TechFM
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论