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 中查看编译状态image.png
上面这个图片也描述了编译的过程
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 的依赖。
Compile Source 中指定所有必须编译的文件,这些文件会根据 Build Setting 和 Build Rules 里的设置来处理。
Link Binary With Libraries 里会列出所有的静态库和动态库,会和编译生成的目标文件进行链接。
build phase 还会把静态资源拷贝到 bundle 里。
可以通过在 build phases 里添加自定义脚本来做些事情,比如像 CocoaPods 所做的那样。
Build Rules
指定不同文件类型如何编译。每条 build rule 指定了该类型如何处理以及输出在哪。可以增加一条新规则对特定文件类型添加处理方法。
Build Settings
在 build 的过程中各个阶段的选项的设置。
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 <UIKit/UIKit.h>
^~~~~~~~~~~~~~~
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=<ViewController.m:9:1>
identifier 'interface' Loc=<ViewController.m:9:2>
identifier 'ViewController' [LeadingSpace] Loc=<ViewController.m:9:12>
l_paren '(' [LeadingSpace] Loc=<ViewController.m:9:27>
r_paren ')' Loc=<ViewController.m:9:28>
at '@' [StartOfLine] Loc=<ViewController.m:10:1>
identifier 'end' Loc=<ViewController.m:10:2>
at '@' [StartOfLine] Loc=<ViewController.m:12:1>
identifier 'implementation' Loc=<ViewController.m:12:2>
identifier 'ViewController' [LeadingSpace] Loc=<ViewController.m:12:17>
minus '-' [StartOfLine] Loc=<ViewController.m:14:1>
l_paren '(' [LeadingSpace] Loc=<ViewController.m:14:3>
void 'void' Loc=<ViewController.m:14:4>
r_paren ')' Loc=<ViewController.m:14:8>
identifier 'viewDidLoad' Loc=<ViewController.m:14:9>
l_brace '{' [LeadingSpace] Loc=<ViewController.m:14:21>
l_square '[' [StartOfLine] [LeadingSpace] Loc=<ViewController.m:15:5>
identifier 'super' Loc=<ViewController.m:15:6>
identifier 'viewDidLoad' [LeadingSpace] Loc=<ViewController.m:15:12>
r_square ']' Loc=<ViewController.m:15:23>
semi ';' Loc=<ViewController.m:15:24>
identifier 'UIView' [StartOfLine] [LeadingSpace] Loc=<ViewController.m:17:5>
star '*' [LeadingSpace] Loc=<ViewController.m:17:12>
identifier 'view' Loc=<ViewController.m:17:13>
equal '=' [LeadingSpace] Loc=<ViewController.m:17:18>
l_square '[' [LeadingSpace] Loc=<ViewController.m:17:20>
l_square '[' Loc=<ViewController.m:17:21>
identifier 'UIView' Loc=<ViewController.m:17:22>
identifier 'alloc' [LeadingSpace] Loc=<ViewController.m:17:29>
r_square ']' Loc=<ViewController.m:17:34>
identifier 'init' [LeadingSpace] Loc=<ViewController.m:17:36>
r_square ']' Loc=<ViewController.m:17:40>
semi ';' Loc=<ViewController.m:17:41>
l_square '[' [StartOfLine] [LeadingSpace] Loc=<ViewController.m:18:5>
identifier 'self' Loc=<ViewController.m:18:6>
period '.' Loc=<ViewController.m:18:10>
identifier 'view' Loc=<ViewController.m:18:11>
identifier 'addSubview' [LeadingSpace] Loc=<ViewController.m:18:16>
colon ':' Loc=<ViewController.m:18:26>
identifier 'view' Loc=<ViewController.m:18:27>
r_square ']' Loc=<ViewController.m:18:31>
semi ';' Loc=<ViewController.m:18:32>
r_brace '}' [StartOfLine] Loc=<ViewController.m:19:1>
at '@' [StartOfLine] Loc=<ViewController.m:20:1>
identifier 'end' Loc=<ViewController.m:20:2>
eof '' Loc=<ViewController.m:20:5>
1 error generated.
可以获得每个 token 的类型,值还有类似 StartOfLine
的位置类型和 Loc=<./ViewController.h:10:1>
这个样的具体位置。
接着进行语法分析(Semantic Analysis)将 token 先按照语法组合成语义生成 VarDecl 节点,然后将这些节点按照层级关系构成抽象语法树 Abstract Syntax Tree (AST)。
打个比方,如果遇到 token 是 = 符号进行赋值的处理,遇到加减乘除就先处理乘除,然后处理加减,这些组合经过嵌套后会生成一个语法数的结构。这个过程完成后会进行赋值操作时类型是不是匹配的处理。
ViewController.m
文件语法分析
In file included from ViewController.m:8:
./ViewController.h:8:9: fatal error: 'UIKit/UIKit.h' file not found
#import <UIKit/UIKit.h>
^~~~~~~~~~~~~~~
TranslationUnitDecl 0x7fb6e0827e08 <<invalid sloc>> <invalid sloc>
|-TypedefDecl 0x7fb6e08286a0 <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128'
| `-BuiltinType 0x7fb6e08283a0 '__int128'
|-TypedefDecl 0x7fb6e0828710 <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128'
| `-BuiltinType 0x7fb6e08283c0 'unsigned __int128'
|-TypedefDecl 0x7fb6e08287b0 <<invalid sloc>> <invalid sloc> implicit SEL 'SEL *'
| `-PointerType 0x7fb6e0828770 'SEL *'
| `-BuiltinType 0x7fb6e0828600 'SEL'
|-TypedefDecl 0x7fb6e0828898 <<invalid sloc>> <invalid sloc> implicit id 'id'
| `-ObjCObjectPointerType 0x7fb6e0828840 'id'
| `-ObjCObjectType 0x7fb6e0828810 'id'
|-TypedefDecl 0x7fb6e0828978 <<invalid sloc>> <invalid sloc> implicit Class 'Class'
| `-ObjCObjectPointerType 0x7fb6e0828920 'Class'
| `-ObjCObjectType 0x7fb6e08288f0 'Class'
|-ObjCInterfaceDecl 0x7fb6e08289d0 <<invalid sloc>> <invalid sloc> implicit Protocol
|-TypedefDecl 0x7fb6e0828d68 <<invalid sloc>> <invalid sloc> implicit __NSConstantString 'struct __NSConstantString_tag'
| `-RecordType 0x7fb6e0828b40 'struct __NSConstantString_tag'
| `-Record 0x7fb6e0828aa0 '__NSConstantString_tag'
|-TypedefDecl 0x7fb6e0834600 <<invalid sloc>> <invalid sloc> implicit __builtin_ms_va_list 'char *'
| `-PointerType 0x7fb6e0828dc0 'char *'
| `-BuiltinType 0x7fb6e0827ea0 'char'
|-TypedefDecl 0x7fb6e0834908 <<invalid sloc>> <invalid sloc> 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 <ViewController.m:9:1, line:10:2> line:9:12
| `-ObjCInterface 0x7fb6e0834960 'ViewController'
`-ObjCImplementationDecl 0x7fb6e0834b80 <line:12:1, line:20:1> line:12:17 ViewController
|-ObjCInterface 0x7fb6e0834960 'ViewController'
`-ObjCMethodDecl 0x7fb6e0834cd0 <line:14:1, line:19:1> line:14:1 - viewDidLoad 'void'
|-ImplicitParamDecl 0x7fb6e0834e88 <<invalid sloc>> <invalid sloc> implicit used self 'ViewController *'
|-ImplicitParamDecl 0x7fb6e0834ef0 <<invalid sloc>> <invalid sloc> implicit _cmd 'SEL':'SEL *'
`-CompoundStmt 0x7fb6e0834fa8 <col:21, line:19:1>
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文件
image.png
Mach-O 文件
记录编译后的可执行文件,对象代码,共享库,动态加载代码和内存转储的文件格式。不同于 xml 这样的文件,它只是二进制字节流,里面有不同的包含元信息的数据块,比如字节顺序,cpu 类型,块大小等。文件内容是不可以修改的,因为在 .app 目录中有个 _CodeSignature 的目录,里面包含了程序代码的签名,这个签名的作用就是保证签名后 .app 里的文件,包括资源文件,Mach-O 文件都不能够更改。
Mach-O 文件包含三个区域
Mach-O Header:包含字节顺序,magic,cpu 类型,加载指令的数量等
Load Commands:包含很多内容的表,包括区域的位置,符号表,动态符号表等。每个加载指令包含一个元信息,比如指令类型,名称,在二进制中的位置等。
Data:最大的部分,包含了代码,数据,比如符号表,动态符号表等。
未完待续~
这篇文章其实主要是学习笔记,后续会精简。
网友评论