iOS 开发中 Objective-C 是 Clang / LLVM 来编译的。swift 是 Swift / LLVM
LLVM是一个模块化和可重用的编译器和工具链技术的集合,Clang 是 LLVM 的子项目,是 C,C++ 和 Objective-C 编译器,其中的 clang static analyzer 主要是进行语法分析,语义分析和生成中间代码
编译流程
- 编译信息写入辅助文件,创建文件架构 .app 文件
- 处理文件打包信息
- 执行 CocoaPod 编译前脚本,checkPods Manifest.lock
- 编译.m文件,使用 CompileC 和 clang 命令
- 链接需要的 Framework
- 编译 xib
- 拷贝 xib ,资源文件
- 编译 ImageAssets
- 处理 info.plist
- 执行 CocoaPod 脚本
- 拷贝标准库
- 创建 .app 文件和签名
其中 3-10 步骤的数量和顺序并不固定,可以在 Build Phases 中可以修改。
本文以下面代码进行演示,文件名是ViewController.m
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIView *view = [[UIView alloc] init];
[self.view addSubview:view];
}
@end
ViewController.m
文件的编译阶段
+- 0: input, "ViewController.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, "x86_64", {5}, image
target 文件结构是
command+B,编译
在 Xcode 编译过后,可以通过
Show the report navigator
里对应 target 的 build 中查看编译状态
上面这个图片也描述了编译的过程
Create product structure
Process product packaging
Run custom shell script ‘Check Pods Manifest.lock’
Compile … 各个项目中的.m文件
Link /Users/… 路径
Copy … 静态文件
Compile asset catalogs
Compile Storyboard file …
Process info.plist
Link Storyboards
Run custom shell script ‘Embed Pods Frameworks’
Run custom shell script ‘Copy Pods Resources’
…
Touch Target.app
Sign Target.app
控制Target 的 Build 过程
在 Xcode 的 Project editor 中的 Build Setting,Build Phases 和 Build Rules 能够控制编译的过程。
Build Phases 构建可执行文件的规则
指定 target 的依赖项目,在 target build 之前需要先 build 的依赖。实际上这并不属于真正的 build phase,在这里,Xcode 只不过将其与 build phase 显示到一块罢了。
Compile Source 中指定所有必须编译的文件,这些文件会根据 Build Setting 和 Build Rules 里的设置来处理。
Link Binary With Libraries 里会列出所有的静态库和动态库,会和编译生成的目标文件进行链接。
build phase 还会把静态资源(例如图片和字体)拷贝到 app bundle 中。需要注意的是,如果图片资源是PNG格式,那么不仅仅对其进行拷贝,还会做一些优化(如果 build settings 中的 PNG 优化是打开的话)。
可以通过在 build phases 里添加自定义脚本来做些事情,比如像 CocoaPods 所做的那样。
build 过程中,要想观察所有已存在的环境变量,你可以在 build phase 中添加一个 "Run Script",并勾选上 "Show environment variables in build log"。
Build Rules
指定不同文件类型如何编译。每条 build rule 指定了该类型如何处理以及输出在哪。如果你需要对特定类型的文件添加处理方法,那么可以在此处添加一条新的规则。
一条 build rule 指定了其应用于哪种类型文件,该类型文件是如何被处理的,以及输出的内容该如何处置。比方说,我们创建了一条预处理规则,该规则将 Objective-C 的实现文件当做输入,解析文件中的注释内容,最后再输出一个 .m 文件,文件中包含了生成的代码。由于我们不能将 .m 文件既当做输入又当做输出,所以我使用了 .mal 后缀,定制的 build rule 如下所示:
Build Settings
在 build settings 中,我们可以配置每个任务(之前在 build log 输出中看到的任务)的详细内容。
你会发现 build 过程的每一个阶段,都有许多选项:从编译、链接一直到 code signing 和 packaging。注意,settings 是如何被分割为不同的部分 -- 其实这大部分会与 build phases 有关联,有时候也会指定编译的文件类型。
pbxproj工程文件
build 过程控制的设置都会被保存在工程文件 .pbxproj 里。在这个文件中可以找 rootObject 的 ID 值
rootObject = 02E680D92560F4BE009EEC2E /* Project object */;
然后根据这个 ID 找到 工程的定义。
/* Begin PBXProject section */
02E680D92560F4BE009EEC2E /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1220;
TargetAttributes = {
02E680E02560F4BE009EEC2E = {
CreatedOnToolsVersion = 12.2;
};
};
};
buildConfigurationList = 02E680DC2560F4BE009EEC2E /* Build configuration list for PBXProject "VScrollContentView" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 02E680D82560F4BE009EEC2E;
productRefGroup = 02E680E22560F4BE009EEC2E /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
02E680E02560F4BE009EEC2E /* VScrollContentView */,
);
};
/* End PBXProject section */
在 targets 里会指向各个 taget 的定义
/* Begin PBXNativeTarget section */
02E680E02560F4BE009EEC2E /* VScrollContentView */ = {
isa = PBXNativeTarget;
buildConfigurationList = 02E680FA2560F4C3009EEC2E /* Build configuration list for PBXNativeTarget "VScrollContentView" */;
buildPhases = (
02E680DD2560F4BE009EEC2E /* Sources */,
02E680DF2560F4BE009EEC2E /* Resources */,
02E680DE2560F4BE009EEC2E /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = VScrollContentView;
productName = VScrollContentView;
productReference = 02E680E12560F4BE009EEC2E /* VScrollContentView.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
这个里面又有更多的 ID 可以得到更多的定义,其中 buildConfigurationList 指向了可用的配置项,包含 Debug 和 Release。可以看到还有 buildPhases,buildRules 和 dependencies 都能够通过这里索引找到更详细的定义。
Clang 编译流程
以ViewController.m为例,首先对文件进行描述
CompileC DerivedData path/ViewController.o Project path/ViewController.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler (in target 'Target Name' from project 'Project Name')
接下来对会更新工作路径
cd Project Path
export LANG\=en_US.US-ASCII
接下来就是实际的编译命令
clang Path/clang -x objective-c -target x86_64....../x86_64/ViewController.o
Clang Static Analyzer静态代码分析
静态分析前会对源代码分词成 Token,这个过程称为词法分析(Lexical Analysis)。Token 可以分为以下几类:
- 关键字:语法中的关键字,if else while for 等。
- 标识符:变量名
- 字面量:值,数字,字符串
- 特殊符号:加减乘除等符号
ViewController.m
文件词法分析
In file included from ViewController.m:8:
./ViewController.h:8:9: fatal error: 'UIKit/UIKit.h' file not found
#import
^~~~~~~~~~~~~~~
at '@' [StartOfLine] Loc=<./ViewController.h:10:1>
identifier 'interface' Loc=<./ViewController.h:10:2>
identifier 'ViewController' [LeadingSpace] Loc=<./ViewController.h:10:12>
colon ':' [LeadingSpace] Loc=<./ViewController.h:10:27>
identifier 'UIViewController' [LeadingSpace] Loc=<./ViewController.h:10:29>
at '@' [StartOfLine] Loc=<./ViewController.h:13:1>
identifier 'end' Loc=<./ViewController.h:13:2>
at '@' [StartOfLine] Loc=
identifier 'interface' Loc=
identifier 'ViewController' [LeadingSpace] Loc=
l_paren '(' [LeadingSpace] Loc=
r_paren ')' Loc=
at '@' [StartOfLine] Loc=
identifier 'end' Loc=
at '@' [StartOfLine] Loc=
identifier 'implementation' Loc=
identifier 'ViewController' [LeadingSpace] Loc=
minus '-' [StartOfLine] Loc=
l_paren '(' [LeadingSpace] Loc=
void 'void' Loc=
r_paren ')' Loc=
identifier 'viewDidLoad' Loc=
l_brace '{' [LeadingSpace] Loc=
l_square '[' [StartOfLine] [LeadingSpace] Loc=
identifier 'super' Loc=
identifier 'viewDidLoad' [LeadingSpace] Loc=
r_square ']' Loc=
semi ';' Loc=
identifier 'UIView' [StartOfLine] [LeadingSpace] Loc=
star '*' [LeadingSpace] Loc=
identifier 'view' Loc=
equal '=' [LeadingSpace] Loc=
l_square '[' [LeadingSpace] Loc=
l_square '[' Loc=
identifier 'UIView' Loc=
identifier 'alloc' [LeadingSpace] Loc=
r_square ']' Loc=
identifier 'init' [LeadingSpace] Loc=
r_square ']' Loc=
semi ';' Loc=
l_square '[' [StartOfLine] [LeadingSpace] Loc=
identifier 'self' Loc=
period '.' Loc=
identifier 'view' Loc=
identifier 'addSubview' [LeadingSpace] Loc=
colon ':' Loc=
identifier 'view' Loc=
r_square ']' Loc=
semi ';' Loc=
r_brace '}' [StartOfLine] Loc=
at '@' [StartOfLine] Loc=
identifier 'end' Loc=
eof '' Loc=
1 error generated.
可以获得每个 token 的类型,值还有类似 StartOfLine
的位置类型和 Loc=<./ViewController.h:10:1>
这个样的具体位置。
接着进行语法分析(Semantic Analysis)将 token 先按照语法组合成语义生成 VarDecl 节点,然后将这些节点按照层级关系构成抽象语法树 Abstract Syntax Tree (AST)。
打个比方,如果遇到 token 是 = 符号进行赋值的处理,遇到加减乘除就先处理乘除,然后处理加减,这些组合经过嵌套后会生成一个语法数的结构。这个过程完成后会进行赋值操作时类型是不是匹配的处理。
clang -fmodules -fsyntax-only -Xclang -ast-dump ViewController.m
ViewController.m
文件语法分析
In file included from ViewController.m:8:
./ViewController.h:8:9: fatal error: 'UIKit/UIKit.h' file not found
#import
^~~~~~~~~~~~~~~
TranslationUnitDecl 0x7fb6e0827e08 <>
|-TypedefDecl 0x7fb6e08286a0 <> implicit __int128_t '__int128'
| `-BuiltinType 0x7fb6e08283a0 '__int128'
|-TypedefDecl 0x7fb6e0828710 <> implicit __uint128_t 'unsigned __int128'
| `-BuiltinType 0x7fb6e08283c0 'unsigned __int128'
|-TypedefDecl 0x7fb6e08287b0 <> implicit SEL 'SEL *'
| `-PointerType 0x7fb6e0828770 'SEL *'
| `-BuiltinType 0x7fb6e0828600 'SEL'
|-TypedefDecl 0x7fb6e0828898 <> implicit id 'id'
| `-ObjCObjectPointerType 0x7fb6e0828840 'id'
| `-ObjCObjectType 0x7fb6e0828810 'id'
|-TypedefDecl 0x7fb6e0828978 <> implicit Class 'Class'
| `-ObjCObjectPointerType 0x7fb6e0828920 'Class'
| `-ObjCObjectType 0x7fb6e08288f0 'Class'
|-ObjCInterfaceDecl 0x7fb6e08289d0 <> implicit Protocol
|-TypedefDecl 0x7fb6e0828d68 <> implicit __NSConstantString 'struct __NSConstantString_tag'
| `-RecordType 0x7fb6e0828b40 'struct __NSConstantString_tag'
| `-Record 0x7fb6e0828aa0 '__NSConstantString_tag'
|-TypedefDecl 0x7fb6e0834600 <> implicit __builtin_ms_va_list 'char *'
| `-PointerType 0x7fb6e0828dc0 'char *'
| `-BuiltinType 0x7fb6e0827ea0 'char'
|-TypedefDecl 0x7fb6e0834908 <> implicit __builtin_va_list 'struct __va_list_tag [1]'
| `-ConstantArrayType 0x7fb6e08348b0 'struct __va_list_tag [1]' 1
| `-RecordType 0x7fb6e08346f0 'struct __va_list_tag'
| `-Record 0x7fb6e0834658 '__va_list_tag'
|-ObjCInterfaceDecl 0x7fb6e0834960 <./ViewController.h:10:1, line:13:2> line:10:12 ViewController
| `-ObjCImplementation 0x7fb6e0834b80 'ViewController'
|-ObjCCategoryDecl 0x7fb6e0834aa0 line:9:12
| `-ObjCInterface 0x7fb6e0834960 'ViewController'
`-ObjCImplementationDecl 0x7fb6e0834b80 line:12:17 ViewController
|-ObjCInterface 0x7fb6e0834960 'ViewController'
`-ObjCMethodDecl 0x7fb6e0834cd0 line:14:1 - viewDidLoad 'void'
|-ImplicitParamDecl 0x7fb6e0834e88 <> implicit used self 'ViewController *'
|-ImplicitParamDecl 0x7fb6e0834ef0 <> implicit _cmd 'SEL':'SEL *'
`-CompoundStmt 0x7fb6e0834fa8
1 error generated.
TranslationUnitDecl 是根节点,表示一个源文件。Decl 表示一个声明,Expr 表示表达式,Literal 表示字面量是特殊的 Expr,Stmt 表示语句。
clang 静态分析是通过建立分析引擎和 checkers 所组成的架构,所有 checker 都是基于底层分析引擎之上,通过分析引擎提供的功能能够编写新的 checker。
这种方式能够方便用户扩展对代码检查规则或者对 bug 类型进行扩展,但是这种架构也有不足,每执行完一条语句后,分析引擎会遍历所有 checker 中的回调函数,所以 checker 越多,速度越慢。
CodeGen 生成 IR 代码
将语法树翻译成 LLVM IR 中间代码,这个过程中还会跟 runtime 桥接。
各种类,方法,成员变量等的结构体的生成,并将其放到对应的Mach-O的section中。
Non-Fragile ABI 合成 OBJC_IVAR_$_ 偏移值常量。
ObjCMessageExpr 翻译成相应版本的 objc_msgSend,super 翻译成 objc_msgSendSuper。
strong,weak,copy,atomic 合成 @property 自动实现 setter 和 getter。
@synthesize 的处理。
生成 block_layout 数据结构
__block 和 __weak
_block_invoke
ARC 处理,插入 objc_storeStrong 和 objc_storeWeak 等 ARC 代码。ObjCAutoreleasePoolStmt 转 objc_autorealeasePoolPush / Pop。自动添加 [super dealloc]。给每个 ivar 的类合成 .cxx_destructor 方法自动释放类的成员变量。
IR 结构
; ModuleID = 'ViewController.m'
source_filename = "ViewController.m"
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-ios14.2.0-simulator"
%0 = type opaque
%1 = type opaque
%struct._class_t = type { %struct._class_t*, %struct._class_t*, %struct._objc_cache*, i8* (i8*, i8*)**, %struct._class_ro_t* }
%struct._objc_cache = type opaque
%struct._class_ro_t = 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 = type { i32, i32, [0 x %struct._objc_method] }
%struct._objc_method = type { i8*, i8*, i8* }
%struct._objc_protocol_list = type { i64, [0 x %struct._protocol_t*] }
%struct._protocol_t = 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 = type { i32, i32, [0 x %struct._ivar_t] }
%struct._ivar_t = type { i64*, i8*, i8*, i32, i32 }
%struct._prop_list_t = type { i32, i32, [0 x %struct._prop_t] }
%struct._prop_t = type { i8*, i8* }
%struct._objc_super = type { i8*, i8* }
@"OBJC_CLASS_$_ViewController" = global %struct._class_t { %struct._class_t* @"OBJC_METACLASS_$_ViewController", %struct._class_t* @"OBJC_CLASS_$_UIViewController", %struct._objc_cache* @_objc_empty_cache, i8* (i8*, i8*)** null, %struct._class_ro_t* @"_OBJC_CLASS_RO_$_ViewController" }, section "__DATA, __objc_data", align 8
@"OBJC_CLASSLIST_SUP_REFS_$_" = private global %struct._class_t* @"OBJC_CLASS_$_ViewController", section "__DATA,__objc_superrefs,regular,no_dead_strip", align 8
@OBJC_METH_VAR_NAME_ = private unnamed_addr constant [12 x i8] c"viewDidLoad\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@OBJC_SELECTOR_REFERENCES_ = internal externally_initialized global i8* getelementptr inbounds ([12 x i8], [12 x i8]* @OBJC_METH_VAR_NAME_, i32 0, i32 0), section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip", align 8
@"OBJC_CLASS_$_UIView" = external global %struct._class_t
@"OBJC_CLASSLIST_REFERENCES_$_" = internal global %struct._class_t* @"OBJC_CLASS_$_UIView", section "__DATA,__objc_classrefs,regular,no_dead_strip", align 8
@OBJC_METH_VAR_NAME_.1 = private unnamed_addr constant [5 x i8] c"view\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@OBJC_SELECTOR_REFERENCES_.2 = internal externally_initialized global i8* getelementptr inbounds ([5 x i8], [5 x i8]* @OBJC_METH_VAR_NAME_.1, i32 0, i32 0), section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip", align 8
@OBJC_METH_VAR_NAME_.3 = private unnamed_addr constant [12 x i8] c"addSubview:\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@OBJC_SELECTOR_REFERENCES_.4 = internal externally_initialized global i8* getelementptr inbounds ([12 x i8], [12 x i8]* @OBJC_METH_VAR_NAME_.3, i32 0, i32 0), section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip", align 8
@_objc_empty_cache = external global %struct._objc_cache
@"OBJC_METACLASS_$_NSObject" = external global %struct._class_t
@"OBJC_METACLASS_$_UIViewController" = external global %struct._class_t
@OBJC_CLASS_NAME_ = private unnamed_addr constant [15 x i8] c"ViewController\00", section "__TEXT,__objc_classname,cstring_literals", align 1
@"_OBJC_METACLASS_RO_$_ViewController" = internal global %struct._class_ro_t { i32 129, i32 40, i32 40, i8* null, i8* getelementptr inbounds ([15 x i8], [15 x i8]* @OBJC_CLASS_NAME_, i32 0, i32 0), %struct.__method_list_t* null, %struct._objc_protocol_list* null, %struct._ivar_list_t* null, i8* null, %struct._prop_list_t* null }, section "__DATA, __objc_const", align 8
@"OBJC_METACLASS_$_ViewController" = global %struct._class_t { %struct._class_t* @"OBJC_METACLASS_$_NSObject", %struct._class_t* @"OBJC_METACLASS_$_UIViewController", %struct._objc_cache* @_objc_empty_cache, i8* (i8*, i8*)** null, %struct._class_ro_t* @"_OBJC_METACLASS_RO_$_ViewController" }, section "__DATA, __objc_data", align 8
@"OBJC_CLASS_$_UIViewController" = external global %struct._class_t
@OBJC_METH_VAR_TYPE_ = private unnamed_addr constant [8 x i8] c"v16@0:8\00", section "__TEXT,__objc_methtype,cstring_literals", align 1
@"_OBJC_$_INSTANCE_METHODS_ViewController" = internal global { i32, i32, [1 x %struct._objc_method] } { i32 24, i32 1, [1 x %struct._objc_method] [%struct._objc_method { i8* getelementptr inbounds ([12 x i8], [12 x i8]* @OBJC_METH_VAR_NAME_, i32 0, i32 0), i8* getelementptr inbounds ([8 x i8], [8 x i8]* @OBJC_METH_VAR_TYPE_, i32 0, i32 0), i8* bitcast (void (%0*, i8*)* @"\01-[ViewController viewDidLoad]" to i8*) }] }, section "__DATA, __objc_const", align 8
@"_OBJC_CLASS_RO_$_ViewController" = internal global %struct._class_ro_t { i32 128, i32 8, i32 8, i8* null, i8* getelementptr inbounds ([15 x i8], [15 x i8]* @OBJC_CLASS_NAME_, i32 0, i32 0), %struct.__method_list_t* bitcast ({ i32, i32, [1 x %struct._objc_method] }* @"_OBJC_$_INSTANCE_METHODS_ViewController" to %struct.__method_list_t*), %struct._objc_protocol_list* null, %struct._ivar_list_t* null, i8* null, %struct._prop_list_t* null }, section "__DATA, __objc_const", align 8
@"OBJC_LABEL_CLASS_$" = private global [1 x i8*] [i8* bitcast (%struct._class_t* @"OBJC_CLASS_$_ViewController" to i8*)], section "__DATA,__objc_classlist,regular,no_dead_strip", align 8
@llvm.compiler.used = appending global [12 x i8*] [i8* bitcast (%struct._class_t** @"OBJC_CLASSLIST_SUP_REFS_$_" to i8*), i8* getelementptr inbounds ([12 x i8], [12 x i8]* @OBJC_METH_VAR_NAME_, i32 0, i32 0), i8* bitcast (i8** @OBJC_SELECTOR_REFERENCES_ to i8*), i8* bitcast (%struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_" to i8*), i8* getelementptr inbounds ([5 x i8], [5 x i8]* @OBJC_METH_VAR_NAME_.1, i32 0, i32 0), i8* bitcast (i8** @OBJC_SELECTOR_REFERENCES_.2 to i8*), i8* getelementptr inbounds ([12 x i8], [12 x i8]* @OBJC_METH_VAR_NAME_.3, i32 0, i32 0), i8* bitcast (i8** @OBJC_SELECTOR_REFERENCES_.4 to i8*), i8* getelementptr inbounds ([15 x i8], [15 x i8]* @OBJC_CLASS_NAME_, i32 0, i32 0), i8* getelementptr inbounds ([8 x i8], [8 x i8]* @OBJC_METH_VAR_TYPE_, i32 0, i32 0), i8* bitcast ({ i32, i32, [1 x %struct._objc_method] }* @"_OBJC_$_INSTANCE_METHODS_ViewController" to i8*), i8* bitcast ([1 x i8*]* @"OBJC_LABEL_CLASS_$" to i8*)], section "llvm.metadata"
; Function Attrs: noinline optnone ssp uwtable
define internal void @"\01-[ViewController viewDidLoad]"(%0* %0, i8* %1) #0 {
%3 = alloca %0*, align 8
%4 = alloca i8*, align 8
%5 = alloca %struct._objc_super, align 8
%6 = alloca %1*, align 8
store %0* %0, %0** %3, align 8
store i8* %1, i8** %4, align 8
%7 = load %0*, %0** %3, align 8
%8 = bitcast %0* %7 to i8*
%9 = getelementptr inbounds %struct._objc_super, %struct._objc_super* %5, i32 0, i32 0
store i8* %8, i8** %9, align 8
%10 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_SUP_REFS_$_", align 8
%11 = bitcast %struct._class_t* %10 to i8*
%12 = getelementptr inbounds %struct._objc_super, %struct._objc_super* %5, i32 0, i32 1
store i8* %11, i8** %12, align 8
%13 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_, align 8, !invariant.load !10
call void bitcast (i8* (%struct._objc_super*, i8*, ...)* @objc_msgSendSuper2 to void (%struct._objc_super*, i8*)*)(%struct._objc_super* %5, i8* %13)
%14 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_", align 8
%15 = bitcast %struct._class_t* %14 to i8*
%16 = call i8* @objc_alloc_init(i8* %15)
%17 = bitcast i8* %16 to %1*
store %1* %17, %1** %6, align 8
%18 = load %0*, %0** %3, align 8
%19 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_.2, align 8, !invariant.load !10
%20 = bitcast %0* %18 to i8*
%21 = call %1* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to %1* (i8*, i8*)*)(i8* %20, i8* %19)
%22 = bitcast %1* %21 to i8*
%23 = notail call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %22) #2
%24 = bitcast i8* %23 to %1*
%25 = load %1*, %1** %6, align 8
%26 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_.4, align 8, !invariant.load !10
%27 = bitcast %1* %24 to i8*
call void bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to void (i8*, i8*, %1*)*)(i8* %27, i8* %26, %1* %25)
%28 = bitcast %1* %24 to i8*
call void @llvm.objc.release(i8* %28) #2, !clang.imprecise_release !10
%29 = bitcast %1** %6 to i8**
call void @llvm.objc.storeStrong(i8** %29, i8* null) #2
ret void
}
declare i8* @objc_msgSendSuper2(%struct._objc_super*, i8*, ...)
declare i8* @objc_alloc_init(i8*)
; Function Attrs: nonlazybind
declare i8* @objc_msgSend(i8*, i8*, ...) #1
; Function Attrs: nounwind
declare i8* @llvm.objc.retainAutoreleasedReturnValue(i8*) #2
; Function Attrs: nounwind
declare void @llvm.objc.release(i8*) #2
; Function Attrs: nounwind
declare void @llvm.objc.storeStrong(i8**, i8*) #2
attributes #0 = { noinline optnone ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="core2" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { nonlazybind }
attributes #2 = { nounwind }
!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8}
!llvm.ident = !{!9}
!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 14, i32 2]}
!1 = !{i32 1, !"Objective-C Version", i32 2}
!2 = !{i32 1, !"Objective-C Image Info Version", i32 0}
!3 = !{i32 1, !"Objective-C Image Info Section", !"__DATA,__objc_imageinfo,regular,no_dead_strip"}
!4 = !{i32 4, !"Objective-C Garbage Collection", i32 0}
!5 = !{i32 1, !"Objective-C Is Simulated", i32 32}
!6 = !{i32 1, !"Objective-C Class Properties", i32 64}
!7 = !{i32 1, !"wchar_size", i32 4}
!8 = !{i32 7, !"PIC Level", i32 2}
!9 = !{!"Apple clang version 12.0.0 (clang-1200.0.32.27)"}
!10 = !{}
IR 语法关键字,如下:
@ - 代表全局变量
% - 代表局部变量
alloca - 指令在当前执行的函数的堆栈帧中分配内存,当该函数返回到其调用者时,将自动释放内存。
i32:- i 是几这个整数就会占几位,i32就是32位4字节
align - 对齐,比如一个 int,一个 char 和一个 int。单个 int 占4个字节,为了对齐只占一个字节的 char需要向4对齐占用4字节空间。
Load - 读出,store 写入
icmp - 两个整数值比较,返回布尔值
br - 选择分支,根据 cond 来转向 label,不根据条件跳转的话类似 goto
indirectbr - 根据条件间接跳转到一个 label,而这个 label 一般是在一个数组里,所以跳转目标是可变的,由运行时决定的
label - 代码标签
之后可以进行 lli 解释执行 LLVM IR。
llc 编译器是专门编译 LLVM IR 的编译器用来生成汇编文件。
调用系统汇编器比如 GNU 的 as 来编译生成 .o Object 文件,接下来就是用链接器链接相关库和 .o 文件一起生成可执行的 .out 或者 exe 文件了。
编译后生成的二进制内容 Link Map File
在 Build Settings 里设置 Write Link Map File 为 Yes 后每次编译都会在指定目录生成这样一个文件。文件内容包含 Object files,Sections,Symbols。
Object files
这个部分的内容都是 .m 文件编译后的 .o 和需要 link 的 .a 文件。前面是文件编号,后面是文件路径。
[ 0] linker synthesized
[ 1] /Users/aibo/Library/Developer/Xcode/DerivedData/VScrollContentView-gwqgglemsizgjsdwjklvhytmwogd/Build/Intermediates.noindex/VScrollContentView.build/Debug-iphonesimulator/VScrollContentView.build/VScrollContentView.app-Simulated.xcent
[ 2] /Users/aibo/Library/Developer/Xcode/DerivedData/VScrollContentView-gwqgglemsizgjsdwjklvhytmwogd/Build/Intermediates.noindex/VScrollContentView.build/Debug-iphonesimulator/VScrollContentView.build/Objects-normal/x86_64/ViewController.o
[ 3] /Users/aibo/Library/Developer/Xcode/DerivedData/VScrollContentView-gwqgglemsizgjsdwjklvhytmwogd/Build/Intermediates.noindex/VScrollContentView.build/Debug-iphonesimulator/VScrollContentView.build/Objects-normal/x86_64/AppDelegate.o
[ 4] /Users/aibo/Library/Developer/Xcode/DerivedData/VScrollContentView-gwqgglemsizgjsdwjklvhytmwogd/Build/Intermediates.noindex/VScrollContentView.build/Debug-iphonesimulator/VScrollContentView.build/Objects-normal/x86_64/main.o
[ 5] /Users/aibo/Library/Developer/Xcode/DerivedData/VScrollContentView-gwqgglemsizgjsdwjklvhytmwogd/Build/Intermediates.noindex/VScrollContentView.build/Debug-iphonesimulator/VScrollContentView.build/Objects-normal/x86_64/SceneDelegate.o
[ 6] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.2.sdk/System/Library/Frameworks//UIKit.framework/UIKit.tbd
[ 7] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.2.sdk/System/Library/Frameworks//Foundation.framework/Foundation.tbd
[ 8] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.2.sdk/usr/lib/libobjc.tbd
Sections
这里描述的是每个 Section 在可执行文件中的位置和大小。每个 Section 的 Segment 的类型分为 __TEXT 代码段和 __DATA 数据段两种。
0x100001E30 0x000005D3 __TEXT __text
0x100002404 0x00000042 __TEXT __stubs
0x100002448 0x0000007E __TEXT __stub_helper
0x1000024C6 0x00000D55 __TEXT __objc_methname
0x10000321B 0x00000070 __TEXT __objc_classname
0x10000328B 0x00000B0F __TEXT __objc_methtype
0x100003D9A 0x00000090 __TEXT __cstring
0x100003E2A 0x00000186 __TEXT __entitlements
0x100003FB0 0x00000048 __TEXT __unwind_info
0x100004000 0x00000018 __DATA_CONST __got
0x100004018 0x00000020 __DATA_CONST __cfstring
0x100004038 0x00000018 __DATA_CONST __objc_classlist
0x100004050 0x00000020 __DATA_CONST __objc_protolist
0x100004070 0x00000008 __DATA_CONST __objc_imageinfo
0x100008000 0x00000058 __DATA __la_symbol_ptr
0x100008058 0x00001328 __DATA __objc_const
0x100009380 0x00000028 __DATA __objc_selrefs
0x1000093A8 0x00000018 __DATA __objc_classrefs
0x1000093C0 0x00000008 __DATA __objc_superrefs
0x1000093C8 0x00000008 __DATA __objc_ivar
0x1000093D0 0x000000F0 __DATA __objc_data
0x1000094C0 0x00000188 __DATA __data
Symbols
Symbols 是对 Sections 进行了再划分。这里会描述所有的 methods,ivar 和字符串,及它们对应的地址,大小,文件编号信息。
dSYM 文件
在每次编译后都会生成一个 dSYM 文件,程序在执行中通过地址来调用方法函数,而 dSYM 文件里存储了函数地址映射,这样调用栈里的地址可以通过 dSYM 这个映射表能够获得具体函数的位置。一般都会用来处理 crash 时获取到的调用栈 .crash 文件将其符号化。
dSYM文件也属于Mach-O文件
Mach-O 文件
记录编译后的可执行文件,对象代码,共享库,动态加载代码和内存转储的文件格式。不同于 xml 这样的文件,它只是二进制字节流,里面有不同的包含元信息的数据块,比如字节顺序,cpu 类型,块大小等。文件内容是不可以修改的,因为在 .app 目录中有个 _CodeSignature 的目录,里面包含了程序代码的签名,这个签名的作用就是保证签名后 .app 里的文件,包括资源文件,Mach-O 文件都不能够更改。
Mach-O 文件包含三个区域
Mach-O Header:包含字节顺序,magic,cpu 类型,加载指令的数量等
Load Commands:包含很多内容的表,包括区域的位置,符号表,动态符号表等。每个加载指令包含一个元信息,比如指令类型,名称,在二进制中的位置等。
Data:最大的部分,包含了代码,数据,比如符号表,动态符号表等。
大概结构如下图所示
Mach-O Header
数据结构
struct mach_header_64 {
uint32_t magic; /* mach magic number identifier */
cpu_type_t cputype; /* cpu specifier */
cpu_subtype_t cpusubtype; /* machine specifier */
uint32_t filetype; /* type of file */
uint32_t ncmds; /* number of load commands */
uint32_t sizeofcmds; /* the size of all the load commands */
uint32_t flags; /* flags */
uint32_t reserved; /* reserved */
};
根据定义与注释,得到以下解释
名称 | 含义 |
---|---|
magic | Mach-O魔数,FAT:0xcafebabeARMv7:0xfeedface,ARM64:0xfeedfacf |
cputype、cpusubtype | CPU架构及子版本 |
filetype | MH_EXECUTABLE(可执行二进制文件)、MH_OBJECT(目标文件)、MH_DYLIB(动态库),有11种宏定义类型,具体可查看源码 |
ncmds | 加载命令的数量 |
sizeofcmds | 所有加载命令的大小 |
flags | dyld加载需要的一些标记,有28种宏定义,具体看源码,其中MH_PIE表示启用ASLR地址空间布局随机化 |
reserved | 64位保留字段 |
Load Commands
//load_commands紧跟mach_header,
//load_commands展开后的数目与总大小已经在mach_header有记录,所有加载指令都是以cmd、cmdsize起头。
//cmd字段用该命令类型的常量表示,有专门的结构;
//cmdsize字段以字节为单位,主要记录偏移量让load command指针进入下一条加载指令,
//32位架构的cmdsize是以4字节的倍数,64位结构的cmdsize是以8字节的倍数(加载指令永远是这样对齐),不够用0填充字节。
//文件中的所有表都遵循这样的规则,这样就可以被映射到内存,否则的话指针不能很好地指向。
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
Load Commands下常见的加载指令:
指令 | 含义 |
---|---|
LC_SEGMENT_64 | 定义一段(Segment),加载后被映射到进程的内存空间中,包括里面的节(Section) |
LC_DYLD_INFO_ONLY | 记录有关链接的信息,包括在__LINKEDIT中动态链接的相关信息的具体偏移与大小(重定位,绑定,弱绑定,懒加载绑定,导出信息等),ONLY表示该指令是程序运行所必需的。 |
LC_SYMTAB | 定义符号表和字符串表,链接文件时被dyld使用,也用于调试器映射符号到源文件。符号表定义的本地符号仅用于调试,而已定义和未定义的external符号被链接器使用 |
LC_DYSYMTAB | 将符号表中给出符号的额外信息提供给dyld |
LC_LOAD_DYLINKER | dyld的默认路径 |
LC_UUID | Mach-O唯一ID |
LC_VERSION_MIN_IPHONES | 系统要求的最低版本 |
LC_SOURCE_VERSION | 构建二进制文件的源代码版本号 |
LC_MAIN | 应用程序入口,dyld的_main函数获取该地址,然后跳转 |
LC_ENCRYPTION_INFO_64 | 文件加密标志,加密内容偏移和大小 |
LC_LOAD_DYLIB | 依赖的动态库,含动态库名,版本号等信息 |
LC_RPATH | @rpath搜索路径 |
LC_DATA_IN_CODE | 定义在代码段内的非指令的表 |
LC_CODE_SIGNATURE | 代码签名信息 |
LC_SEGMENT_64段数据结构(说明附在注释部分):
/*
* The 64-bit segment load command indicates that a part of this file is to be
* mapped into a 64-bit task's address space. If the 64-bit segment has
* sections then section_64 structures directly follow the 64-bit segment
* command and their size is reflected in cmdsize.
*/
struct segment_command_64 { /* for 64-bit architectures */
uint32_t cmd; /* Load Command类型 */
uint32_t cmdsize; /*包含的所有section结构体的大小 */
char segname[16]; /* 段名 */
uint64_t vmaddr; /* 映射到虚拟地址的偏移 */
uint64_t vmsize; /* 映射到虚拟地址的大小 */
uint64_t fileoff; /* 相对于当前架构文件的偏移 */
uint64_t filesize; /* 文件大小 */
vm_prot_t maxprot; /* 段页面的最高内存保护 */
vm_prot_t initprot; /* 初始内存保护 */
uint32_t nsects; /* 包含的section数 */
uint32_t flags; /* 段页面标志 */
};
该数据结构的段主要有以下4种:
段 | 含义 |
---|---|
_PAGEZERO | 空指针陷阱段,映射到虚拟内存空间第一页,捕捉对NULL指针的引用 |
_TEXT | 代码段、只读数据段 |
_DATA | 读取和写入数据段 |
_LINKEDIT | dyld需要使用的信息,包括重定位、绑定、懒加载信息等 |
Data
Load Commands区域下来接着就是DATA区域,展开Load Commands下的LC_SEGMENT_64可以看到多个Section64,各个Section的具体信息可以在Load Commands紧接着的部分查看,它们是一一对应的:
section的数据结构如下:
struct section_64 { /* for 64-bit architectures */
char sectname[16]; /* 节名 */
char segname[16]; /* 所属段名 */
uint64_t addr; /* 映射到虚拟地址的偏移 */
uint64_t size; /* 节的大小 */
uint32_t offset; /* 节在当前架构文件中的偏移 */
uint32_t align; /* 节的字节对齐大小n,2^n */
uint32_t reloff; /* 重定位入口的文件偏移 */
uint32_t nreloc; /* 重定位入口个数 */
uint32_t flags; /* 节的类型和属性*/
uint32_t reserved1; /* reserved (for offset or index) */
uint32_t reserved2; /* reserved (for count or sizeof) */
uint32_t reserved3; /* 保留位,以上两同理 */
};
section节已经是最小的分类,大部分内容集中在__TEXT,__DATA这两段中,部分内容如下:
__TEXT节 | 含义 |
---|---|
__text | 程序可执行代码区域 |
__stubs | 间接符号存根,用于跳转到懒加载指针表 |
__stubs_helper | 懒加载符号加载辅助函数 |
__cstring | 只读的C字符串,包含OC的部分字符串和属性名 |
...... | ...... |
__DATA | 含义 |
---|---|
__nl_symbol_ptr | 非懒加载指针表,dyld加载时立即绑定值 |
__la_symbol_ptr | 懒加载指针表,第1次调用才绑定值 |
__got | 非懒加载全局指针表 |
__mod_init_func | constructor函数 |
__cfstring | OC字符串 |
任意的片段
使用链接符号 -sectcreate 我们可以给可执行文件以 section 的方式添加任意的数据。这就是如何将一个 Info.plist 文件添加到一个独立的可执行文件中的方法。Info.plist 文件中的数据需要放入到 __TEXT segment 里面的一个 __info_plist section 中。可以将 -sectcreate segname sectname file 传递给链接器(通过将下面的内容传递给 clang):
-Wl,-sectcreate,__TEXT,__info_plist,path/to/Info.plist
同样,-sectalign 规定了对其方式。如果你添加的是一个全新的 segment,那么需要通过 -segprot 来规定 segment 的保护方式 (读/写/可执行)。这些所有内容在链接器的帮助文档中都有,例如 ld(1)。
我们可以利用定义在 /usr/include/mach-o/getsect.h 中的函数 getsectdata() 得到 section,例如 getsectdata() 可以得到指向 section 数据的一个指针,并返回相关 section 的长度。
可执行文件的链接
A.h
#import
@interface A : NSObject
- (void)doSomething;
@end
A.m
#import "A.h"
@implementation A
- (void)doSomething{
NSLog(@"just try");
}
@end
B.m
#import "A.h"
int main(int argc, char *argv[])
{
@autoreleasepool {
A *a = [[A alloc] init];
[a doSomething];
return 0;
}
}
先编译多个文件
xcrun clang -c A.m
xcrun clang -c B.m
再将编译后的文件链接起来,这样就可以生成 a.out 可执行文件了。
xcrun clang A.o B.o -Wl,`xcrun —show-sdk-path`/System/Library/Frameworks/Foundation.framework/Foundation
./a.out
2020-11-24 22:26:52.729 a.out[31482:350561] just try
dyld动态链接
生成可执行文件后就是在启动时进行动态链接了,进行符号和地址的绑定。首先会加载所依赖的 dylibs,修正地址偏移,因为 iOS 会用 ASLR 来做地址偏移避免攻击,确定 Non-Lazy Pointer 地址进行符号地址绑定,加载所有类和分类,最后执行 load 方法和 clang attribute 的 constructor 修饰函数。
每个函数,全局变量和类都是通过符号的形式来定义和使用的,当把目标文件链接成一个执行文件时,链接器在目标文件和动态库之间对符号做解析处理。
符号表会规定它们的符号,使用 nm 工具看看
xcrun nm -nm SayHi.o
(undefined) external _OBJC_CLASS_$_A
(undefined) external _objc_alloc_init
(undefined) external _objc_autoreleasePoolPop
(undefined) external _objc_autoreleasePoolPush
(undefined) external _objc_msgSend
0000000000000000 (__TEXT,__text) external _main
0000000000000060 (__DATA,__objc_classrefs) non-external _OBJC_CLASSLIST_REFERENCES_$_
0000000000000078 (__DATA,__objc_selrefs) non-external _OBJC_SELECTOR_REFERENCES_
- OBJC_CLASS_$_A 表示 A 的 OC 符号。
- (undefined) external 表示未实现非私有,如果是私有就是 non-external。
- external _main 表示 main() 函数,处理 0 地址,将要到 __TEXT,__text section
再看看 A
xcrun nm -nm A.o
(undefined) external _NSLog
(undefined) external _OBJC_CLASS_$_NSObject
(undefined) external _OBJC_METACLASS_$_NSObject
(undefined) external ___CFConstantStringClassReference
(undefined) external __objc_empty_cache
0000000000000000 (__TEXT,__text) non-external -[A doSomething]
0000000000000058 (__DATA,__objc_const) non-external __OBJC_METACLASS_RO_$_A
00000000000000a0 (__DATA,__objc_const) non-external __OBJC_$_INSTANCE_METHODS_A
00000000000000c0 (__DATA,__objc_const) non-external __OBJC_CLASS_RO_$_A
0000000000000108 (__DATA,__objc_data) external _OBJC_METACLASS_$_A
0000000000000130 (__DATA,__objc_data) external _OBJC_CLASS_$_A
因为 undefined 符号表示该文件类未实现的,所以在目标文件和 Fundation framework 动态库做链接处理时,链接器会尝试解析所有的 undefined 符号。
链接器通过动态库解析成符号会记录是通过哪个动态库解析的,路径也会一起记录。对比下 a.out 符号表看看是怎么解析符号的。
xcrun nm -nm a.out
(undefined) external _NSLog (from Foundation)
(undefined) external _OBJC_CLASS_$_NSObject (from libobjc)
(undefined) external _OBJC_METACLASS_$_NSObject (from libobjc)
(undefined) external ___CFConstantStringClassReference (from CoreFoundation)
(undefined) external __objc_empty_cache (from libobjc)
(undefined) external _objc_alloc_init (from libobjc)
(undefined) external _objc_autoreleasePoolPop (from libobjc)
(undefined) external _objc_autoreleasePoolPush (from libobjc)
(undefined) external _objc_msgSend (from libobjc)
(undefined) external dyld_stub_binder (from libSystem)
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
0000000100003eb0 (__TEXT,__text) non-external -[A doSomething]
0000000100003ee0 (__TEXT,__text) external _main
0000000100008020 (__DATA,__objc_const) non-external __OBJC_METACLASS_RO_$_A
0000000100008068 (__DATA,__objc_const) non-external __OBJC_$_INSTANCE_METHODS_A
0000000100008088 (__DATA,__objc_const) non-external __OBJC_CLASS_RO_$_A
00000001000080e0 (__DATA,__objc_data) external _OBJC_METACLASS_$_A
0000000100008108 (__DATA,__objc_data) external _OBJC_CLASS_$_A
0000000100008130 (__DATA,__data) non-external __dyld_private
undefined 的符号,有了更多信息,可以知道在哪个动态库能够找到。
通过 otool 可以找到所需库在哪
xcrun otool -L a.out
a.out:
/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 1677.104.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1)
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1677.104.0)
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
libSystem 里有很多我们熟悉的lib
- libdispatch:GCD
- libsystem_c:C语言库
- libsystem_blocks:Block
- libcommonCrypto:加密,比如md5
动态链接编辑器
dylib 这种格式的表示是动态链接的,编译的时候不会被编译到执行文件中,在程序执行的时候才 link,这样就不用算到包的大小里,而且也能够不更新执行程序就能够更新库。
有一些环境变量对于 dyld 的输出信息非常有用。首先,如果设置了 DYLD_PRINT_LIBRARIES,那么 dyld 将会打印出什么库被加载了:
(export DYLD_PRINT_LIBRARIES=; ./a.out )
dyld: loaded: <69390B5E-90A6-326E-B0E6-C124932BC142> /Users/aibo/Desktop/OC/OC/./a.out
dyld: loaded: <7C69F845-F651-3193-8262-5938010EC67D> /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation
dyld: loaded: <0A6C8BA1-30FD-3D10-83FD-FF29E221AFFE> /usr/lib/libSystem.B.dylib
dyld: loaded:
...
数数还挺多的,因为 Fundation 还会依赖一些其它的动态库,其它的库还会再依赖更多的库,这样相互依赖的符号会很多,需要处理的时间也会比较长,这里系统上的动态链接器会使用共享缓存,共享缓存在 /var/db/dyld/。当加载 Mach-O 文件时动态链接器会先检查共享内存是否有。每个进程都会在自己地址空间映射这些共享缓存,这样可以优化启动速度。
dyld 做了些什么事
- kernel 做启动程序初始准备,开始由dyld负责。
- 基于非常简单的原始栈为 kernel 设置进程来启动自身。
- 使用共享缓存来处理递归依赖带来的性能问题,ImageLoader 会读取二进制文件,其中包含了我们的类,方法等各种符号。
- 立即绑定 non-lazy 的符号并设置用于 lazy bind 的必要表,将这些库 link 到执行文件里。
- 为可执行文件运行静态初始化。
- 设置参数到可执行文件的 main 函数并调用它。
- 在执行期间,通过绑定符号处理对 lazily-bound 符号存根的调用提供 runtime 动态加载服务,并为gdb和其它调试器提供钩子以获得关键信息。runtime 会调用 map_images 做解析和处理,load_images 来调用 call_load_methods 方法遍历所有加载了的 Class,按照继承层级依次调用 +load 方法。
- 在 mian 函数返回后运行 static terminator。
- 在某些情况下,一旦 main 函数返回,就需要调用 libSystem 的 _exit。
在 +load 方法里断点查看走到这里调用的堆栈如下:
0 +[someclass load]
1 call_class_loads()
2 ::call_load_methods
3 ::load_images(const char *path __unused, const struct mach_header *mh)
4 dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*)
11 _dyld_start
prepare_load_methods 这个方法会获取所有类的列表然后收集其中的 +load 方法,在代码里可以发现 Class 的 +load 是先执行的,然后执行 Category 的。为什么这样做,原因可以通过 prepare_load_methods 这个方法看出,在遍历 Class 的 +load 方法时会执行 schedule_class_load 这个方法,这个方法会递归到根节点来满足 Class 收集完整关系树的需求。
最后 call_load_methods 会创建一个 autoreleasePool 使用函数指针来动态调用类和 Category 的 +load 方法。
未完待续~
这篇文章其实主要是学习笔记,后续会精简。
参考:
深入剖析-iOS-编译-Clang---LLVM
Build 过程
编译器
Mach-O 可执行文件