C,C++,OC等语言,都是使用的编译器,生成相关的可执行文件, 与之对应的Python,Shell等脚本性语言,它们使用的是解释器, 解释器会在运行时解释执行代码,获取一段代码后就会将其翻译成目标代码(就是字节码(Bytecode)),然后一句一句地执行目标代码。但是跑起来之后可以不用重启启动编译,直接修改代码即可看到效果,类似热更新,可以帮我们缩短整个程序的开发周期和功能更新周期
总结来说:
- 采用编译器生成机器码执行的好处是效率高,缺点是调试周期长
- 解释器执行的好处是编写调试方便,缺点是执行效率低
随着移动应用的发展,很多客户要求编译库时用 libc++
来代替 libstdc++
.
libc++
和 libstdc++
这两个库有关系呢?
两个都是 C++ 标准库,libc++
是针对 Clang 编译器
特别重写的 C++ 标准库,而libstdc++
则是 GCC
的对应 C++ 标准库了。
Android
和iOS
都放弃了 GCC
,全面转向Clang
一、简要的介绍一下 Clang
Clang
是一个 C、C++、Objective-C 和 Objective-C++ 编程语言的编译器前端
,采用底层虚拟机(LLVM
)作为后端.
Clang
是LLVM
的一个子项目
至于为什么有了 GCC 还要开发 Clang?Clang 相比 GCC 又有什么优势呢?
1)Clang 采用的是 BSD 协议的许可证,而 GCC 采用的是 GPL 协议,显然前者更为宽松
2)Clang 是一个高度模块化
开发的轻量级编译器
,编译速度快、占用内存小、有着友好的出错提示;Clang生成的AST所占用的内存是GCC的五分之一左右
3)诊断信息可读性强:在编译过程中,Clang创建并保留了大量详细的元数据(metadata),有利于调试和错误诊断
二、LLVM
LLVM项目是模块化、可重用的编译器
以及工具链技术的集合。
以C++编写而成,用于优化以任意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time),对开发者保持开放
编译器的工作原理
image.png- Frontend : 前端负责解析源代码
* 词法分析、语法分析、语义分析、生成中间代码 - Optimizer: 优化器负责对中间代码进行优化,试图使代码更高效
- Backend:后端负责将优化器优化后的中间代码转换为目标机器的代码,这一过程后端会最大化的利用目标机器的特殊指令,以提高代码的性能
- 不同的前后端使用统一的中间代码LLVM Intermediate Representation (LLVM IR)
- 如果需要支持一种新的编程语言,那么只需要实现一个新的前端
- 如果需要支持一种新的硬件设备,那么只需要实现一个新的后端
- 优化阶段是一个通用的阶段,它针对的是统一的LLVM IR,不论是新的编程语言,还是支持新的硬件设备,都不需要对优化阶段做修改
- 相比之下,GCC的前后端没有分的太开,前后端耦合在一起,所以GCC为了支持一门新的语言、或者新的设备平台就变得特别困难
- LLVM现在被作为实现各种静态和运行时编译语言的通用基础结构(GCC家族、Java、.NET、Python、Ruby、Scheme、Haskell等)
三、从iOS角度理解LLVM
在iOS中,不管是Swift还是OC,生成的中间代码都是LLVM IR,都是构建在LLVM架构上的,标准的三段式设计
image.png- OC采用的编译器前端是Clang
- Swift采用的编译器前端是Swiftc
iOS中可能遇到的Clang常用指令
iOS中可能常用的Clang指令,如果想要全部的Clang指令集,可以在terminal终端中输入clang --help查看,也可以直接在LLVM
官网的Clang Document查询
Clang命令 | 释义 |
---|---|
-ccc-print-phases | 打印源码的编译阶段,得到的打印结果就是整个源码到机器代码的整体流程步骤 |
-rewrite-objc | 将OC 源码编译成C++ 源码 |
-E | 查看预处理阶段详细步骤(在terminal 中查看,也可以生成一个新文件 : clang -E main.m >> new_main.m ) 。>>就是文件重定向 |
-c | 必须在-E 之后才可以使用,例如clang -E main.m -c 。只进行预处理,编译,汇编步骤,不进行链接步骤。执行完成后生成的是.o 链接文件 |
-S | 只进行预处理和编译步骤,不进行汇编,链接等后续步骤。执行完成后,生成的是.s 汇编代码文件 |
-o <file> | 完成预编译-->编译-->汇编-->链接 后,生成可执行文件到<file> 文件 |
-g | 在生成的可执行文件中,包含标准的调试信息 |
-i <路径>(应该是大写i最标准) | 在头文件中的搜索路径中添加<路径>
|
-fmodules | 启用模块化语言特性 |
-fsyntax-only | 编译器并不生成代码,后续的操作只是语法级别的修改 |
-Xclang <arg> | 向clang 编译器传递参数<arg>
|
-dump-tokens | 运行预处理器,将源码内部拆分成各种单词(Token ) |
-ast-dump | 构建抽象语法树AST ,然后对其进行拆解和调试 |
-fobjc-arc | 生成Objective-C指针的retain 和release 的调用 |
-emit-llvm | 对汇编文件和目标文件生成LLVM IR 代码 |
Objective-C原文件的编译过程
image.png步骤1 : 使用xcode新建一个空的macOS下的commond Line Tool命令行工具
image.png image.png步骤2 : 打开terminal
终端,进入到刚创建的这个项目中main.m
所在的文件夹下, 在terminal
终端中输入clang
的查看详细编译步骤
的指令
clang -ccc-print-phases main.m
查看结果:
源文件(0)-->预编译(1)-->编译(2)-->汇编(3,4)-->链接(5)-->生成可执行文件(6)
+- 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, "x86_64", {5}, image
0:输入文件:找到源文件
1.预编译处理阶段:这个过程处理包括宏的替换,头文件的导入。
2.编译阶段:进行词法分析,语法分析,检查语法是否正确,最终生成IR
.
3.后端:这里LLVM
会通过一个一个的Pass
去优化,每个Pass
做一些事情,最终生成汇编代码
。
4.生成目标文件
5.链接:链接需要的动态库和静态库,生成可执行文件
。
6.通过不同的架构,生成对应的可执行文件
如input
、preprocessor
、compiler
、backend
、assembler
、linker
、bind-arch
,这些关键词表示的是编译中的操作名称
如main.m
、{0}.....{5}
,这些东西表示的是这一步操作中要读取的文件,也就是上一步操作的结果文件
如objective-c
、objective-c-cpp-output
、ir
、assembler
、object
、image
等关键词就是本步操作完成后,生成的文件,也就是上面2
中说的上一步操作的结果文件
对于这个工程,一共有0~6
一共7
个阶段。这就是main.m
这个文件从源码到机器语言的总共经历的流程,下面开始按照顺序来逐个说明这些流程。
1. 预处理阶段
执行如下命令
clang -E main.m
也可以如下命令, 预处理结果就会保存在main.2文件里
clang -E main.m >> main2.m
预处理的结果
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 2,b = 3;
// 宏定义直接被替换成10
printf("%d",a + b + 10);
}
return 0;
}
这个过程的处理包括宏的替换,头文件的导入,以及类似#if
的处理
2. 编译阶段
编译阶段的主要任务有3个 :
- 词法分析生成Token
- 语法分析生成抽象语法树AST
- 生成中间代码IR
2.1 词法分析生成Token
预处理阶段完成后就会进入词法分析,将预处理阶段传过来的源码的字符序列一个一个的读入源程序,然后根据构词规则转换成单词序列(Token),比如大小括号,等于号还有字符串...
查看词法分析结果的命令
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
词法分析结果:
// 定义一个宏
#define TEST_NUM 10
int main(int argc, const char * argv[]) {
@autoreleasep' Loc=<main.m:9:1>
int 'int' [StartOfLine] Loc=<main.m:14:1> /* 这里14表示第14行,1表示第一个单词的开始位置*/
identifier 'main' [LeadingSpace] Loc=<main.m:14:5>
l_paren '(' Loc=<main.m:14:9>
int 'int' Loc=<main.m:14:10>
identifier 'argc' [LeadingSpace] Loc=<main.m:14:14>
comma ',' Loc=<main.m:14:18>
const 'const' [LeadingSpace] Loc=<main.m:14:20>
char 'char' [LeadingSpace] Loc=<main.m:14:26>
star '*' [LeadingSpace] Loc=<main.m:14:31>
identifier 'argv' [LeadingSpace] Loc=<main.m:14:33>
l_square '[' Loc=<main.m:14:37>
r_square ']' Loc=<main.m:14:38>
r_paren ')' Loc=<main.m:14:39>
l_brace '{' [LeadingSpace] Loc=<main.m:14:41>
at '@' [StartOfLine] [LeadingSpace] Loc=<main.m:15:5>
identifier 'autoreleasepool' Loc=<main.m:15:6>
l_brace '{' [LeadingSpace] Loc=<main.m:15:22>
int 'int' [StartOfLine] [LeadingSpace] Loc=<main.m:16:9>
identifier 'a' [LeadingSpace] Loc=<main.m:16:13>
equal '=' [LeadingSpace] Loc=<main.m:16:15>
numeric_constant '2' [LeadingSpace] Loc=<main.m:16:17>
comma ',' Loc=<main.m:16:18>
identifier 'b' Loc=<main.m:16:19>
equal '=' [LeadingSpace] Loc=<main.m:16:21>
numeric_constant '3' [LeadingSpace] Loc=<main.m:16:23>
semi ';' Loc=<main.m:16:24>
identifier 'printf' [StartOfLine] [LeadingSpace] Loc=<main.m:17:9>
l_paren '(' Loc=<main.m:17:15>
string_literal '"%d"' Loc=<main.m:17:16>
comma ',' Loc=<main.m:17:20>
identifier 'a' Loc=<main.m:17:21>
plus '+' [LeadingSpace] Loc=<main.m:17:23>
identifier 'b' [LeadingSpace] Loc=<main.m:17:25>
plus '+' [LeadingSpace] Loc=<main.m:17:27>
numeric_constant '10' [LeadingSpace] Loc=<main.m:17:29 <Spelling=main.m:12:18>>
r_paren ')' Loc=<main.m:17:37>
semi ';' Loc=<main.m:17:38>
r_brace '}' [StartOfLine] [LeadingSpace] Loc=<main.m:18:5>
return 'return' [StartOfLine] [LeadingSpace] Loc=<main.m:19:5>
numeric_constant '0' [LeadingSpace] Loc=<main.m:19:12>
semi ';' Loc=<main.m:19:13>
r_brace '}' [StartOfLine] Loc=<main.m:20:1>
eof '' Loc=<main.m:20:2>
这里注意,空格也算一个位置
词法分析其实就是将代码按照一定规则拆分成一个一个Token
2.2 语法分析生成抽象语法树AST
词法分析完成之后就是语法分析,它的任务是验证语法是否正确。在词法分析的基础上将单词序列组合成各类语法短语,如程序,语句,表达式,等等,然后将所有节点组成抽象语法树,语法分析程序判断源程序在结构上是否正确
查看语法分析结果的命令
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
语法分析结果:
TranslationUnitDecl 0x7fe402037e08 <<invalid sloc>> <invalid sloc> <undeserialized declarations>
|-TypedefDecl 0x7fe4020386a0 <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128'
| `-BuiltinType 0x7fe4020383a0 '__int128'
|-TypedefDecl 0x7fe402038710 <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128'
| `-BuiltinType 0x7fe4020383c0 'unsigned __int128'
|-TypedefDecl 0x7fe4020387b0 <<invalid sloc>> <invalid sloc> implicit SEL 'SEL *'
| `-PointerType 0x7fe402038770 'SEL *'
| `-BuiltinType 0x7fe402038600 'SEL'
|-TypedefDecl 0x7fe402038898 <<invalid sloc>> <invalid sloc> implicit id 'id'
| `-ObjCObjectPointerType 0x7fe402038840 'id'
| `-ObjCObjectType 0x7fe402038810 'id'
|-TypedefDecl 0x7fe402038978 <<invalid sloc>> <invalid sloc> implicit Class 'Class'
| `-ObjCObjectPointerType 0x7fe402038920 'Class'
| `-ObjCObjectType 0x7fe4020388f0 'Class'
|-ObjCInterfaceDecl 0x7fe4020389d0 <<invalid sloc>> <invalid sloc> implicit Protocol
|-TypedefDecl 0x7fe402038d68 <<invalid sloc>> <invalid sloc> implicit __NSConstantString 'struct __NSConstantString_tag'
| `-RecordType 0x7fe402038b40 'struct __NSConstantString_tag'
| `-Record 0x7fe402038aa0 '__NSConstantString_tag'
|-TypedefDecl 0x7fe402076000 <<invalid sloc>> <invalid sloc> implicit __builtin_ms_va_list 'char *'
| `-PointerType 0x7fe402038dc0 'char *'
| `-BuiltinType 0x7fe402037ea0 'char'
|-TypedefDecl 0x7fe402076308 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list 'struct __va_list_tag [1]'
| `-ConstantArrayType 0x7fe4020762b0 'struct __va_list_tag [1]' 1
| `-RecordType 0x7fe4020760f0 'struct __va_list_tag'
| `-Record 0x7fe402076058 '__va_list_tag'
|-ImportDecl 0x7fe402076b30 <main.m:9:1> col:1 implicit Darwin.C.stdio
`-FunctionDecl 0x7fe402076df0 <line:14:1, line:20:1> line:14:5 main 'int (int, const char **)'
|-ParmVarDecl 0x7fe402076b88 <col:10, col:14> col:14 argc 'int'
|-ParmVarDecl 0x7fe402076ca0 <col:20, col:38> col:33 argv 'const char **':'const char **'
`-CompoundStmt 0x7fe405097f68 <col:41, line:20:1>
|-ObjCAutoreleasePoolStmt 0x7fe405097f20 <line:15:5, line:18:5>
| `-CompoundStmt 0x7fe405097f00 <line:15:22, line:18:5>
| |-DeclStmt 0x7fe405097cb8 <line:16:9, col:24>
| | |-VarDecl 0x7fe402076f40 <col:9, col:17> col:13 used a 'int' cinit
| | | `-IntegerLiteral 0x7fe402076fa8 <col:17> 'int' 2
| | `-VarDecl 0x7fe405097800 <col:9, col:23> col:19 used b 'int' cinit
| | `-IntegerLiteral 0x7fe405097868 <col:23> 'int' 3
| `-CallExpr 0x7fe405097ea0 <line:17:9, col:37> 'int'
| |-ImplicitCastExpr 0x7fe405097e88 <col:9> 'int (*)(const char *, ...)' <FunctionToPointerDecay>
| | `-DeclRefExpr 0x7fe405097cd0 <col:9> 'int (const char *, ...)' Function 0x7fe405097890 'printf' 'int (const char *, ...)'
| |-ImplicitCastExpr 0x7fe405097ee8 <col:16> 'const char *' <NoOp>
| | `-ImplicitCastExpr 0x7fe405097ed0 <col:16> 'char *' <ArrayToPointerDecay>
| | `-StringLiteral 0x7fe405097d28 <col:16> 'char [3]' lvalue "%d"
| `-BinaryOperator 0x7fe405097e28 <col:21, line:12:18> 'int' '+'
| |-BinaryOperator 0x7fe405097de8 <line:17:21, col:25> 'int' '+'
| | |-ImplicitCastExpr 0x7fe405097db8 <col:21> 'int' <LValueToRValue>
| | | `-DeclRefExpr 0x7fe405097d48 <col:21> 'int' lvalue Var 0x7fe402076f40 'a' 'int'
| | `-ImplicitCastExpr 0x7fe405097dd0 <col:25> 'int' <LValueToRValue>
| | `-DeclRefExpr 0x7fe405097d80 <col:25> 'int' lvalue Var 0x7fe405097800 'b' 'int'
| `-IntegerLiteral 0x7fe405097e08 <line:12:18> 'int' 10
`-ReturnStmt 0x7fe405097f58 <line:19:5, col:12>
`-IntegerLiteral 0x7fe405097f38 <col:12> 'int' 0
- FunctionDecl : 方法节点
-FunctionDecl 0x7fe402076df0 <line:14:1, line:20:1> line:14:5 main 'int (int, const char **)'
方法节点的代码范围是从第14行第1个字符到第20行第1个字符
line:14:5 main 'int (int, const char **)'
从第14行第5个字符的位置开始,是main方法的位置,第一个int表示main方法的返回值类型,(int, const char **)表示main方法的参数类型
- ParmVarDecl : 参数节点
<col:10, col:14> :
参数节点因为与main方法在同一行,所以不再说明是第14行。
直接说明第一个参数的位置是第10个字符开始,到第14个字符为止。
argc 'int' :
参数名称是argc,参数类型是int。
上述是第一个参数的解释,下面的第二个参数相同,不再赘述。
- CompoundStmt : 围栏,也可以说是范围
代表的就是main方法的{ }函数块区域 - ObjCAutoreleasePoolStmt : 自动释放池
- VarDecl : 变量节点
- CallExpr : 调用函数,这一行后面的int代表这个函数的返回值的类型是int
- BinaryOperator : 函数的第二个参数,这叫字节运算符。这一行表示这个字节运算符是加法运算。表明函数的第二个参数是一个加法运算的结果
- ReturnStmt : 返回节点
- IntegerLiteral : 整型
2.3 生成中间代码IR
完成以上步骤后就开始生成中间代码IR了,代码生成器(Code Generation),会将语法树自顶向下遍历逐步翻译成LLVM IR
Objective C代码在这一步会进行runtime的桥接:property合成,ARC处理等
LLVM IR
有3种表示形式(但本质上是等价的,就好比水可以有气体、液体、固体3种形态)
- text:便于阅读的本文格式,拓展名
.II
- memory: 内存格式
- bitcode: 二进制格式,拓展名
.bc
// 生成中间代码IR的命令,会在main.m的 同级目录下生成main.ll文件
// text 文本格式
clang -S -fobjc-arc -emit-llvm main.m
// 二进制格式
clang -c -fobjc-arc -emit-llvm main.m
IR的基本语法
语法 | 释义 |
---|---|
; | 注释以分号开头 |
@ | 全局标识以@开头 |
% | 局部标识以%开头 |
alloca | 在当前函数栈帧中分配内存 |
align | 内存对齐 |
i32 | 32bit,4个字节的意思 |
store | 写入内存 |
load | 载入内存 |
call | 调用函数 |
ret | 返回 |
查看中间代码
; ModuleID = 'main.m'
source_filename = "main.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-macosx10.15.0"
@.str = private unnamed_addr constant [3 x i8] c"%d\00", align 1
; Function Attrs: noinline optnone ssp uwtable
define i32 @main(i32 %0, i8** %1) #0 {
%3 = alloca i32, align 4
%4 = alloca i32, align 4
%5 = alloca i8**, align 8
%6 = alloca i32, align 4
%7 = alloca i32, align 4
store i32 0, i32* %3, align 4
store i32 %0, i32* %4, align 4
store i8** %1, i8*** %5, align 8
%8 = call i8* @llvm.objc.autoreleasePoolPush() #1
store i32 2, i32* %6, align 4
store i32 3, i32* %7, align 4
%9 = load i32, i32* %6, align 4
%10 = load i32, i32* %7, align 4
%11 = add nsw i32 %9, %10
%12 = add nsw i32 %11, 10
%13 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i64 0, i64 0), i32 %12)
call void @llvm.objc.autoreleasePoolPop(i8* %8)
ret i32 0
}
; Function Attrs: nounwind
declare i8* @llvm.objc.autoreleasePoolPush() #1
declare i32 @printf(i8*, ...) #2
; Function Attrs: nounwind
declare void @llvm.objc.autoreleasePoolPop(i8*) #1
attributes #0 = { noinline optnone ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "darwin-stkchk-strong-link" "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" "probe-stack"="___chkstk_darwin" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { nounwind }
attributes #2 = { "correctly-rounded-divide-sqrt-fp-math"="false" "darwin-stkchk-strong-link" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "probe-stack"="___chkstk_darwin" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7}
!llvm.ident = !{!8}
!0 = !{i32 2, !"SDK Version", [3 x i32] [i32 10, i32 15, i32 6]}
!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 Class Properties", i32 64}
!6 = !{i32 1, !"wchar_size", i32 4}
!7 = !{i32 7, !"PIC Level", i32 2}
!8 = !{!"Apple clang version 12.0.0 (clang-1200.0.32.21)"}
2.4 优化器
Clang是LLVM的前端,Clang做了2.2预处理阶段和2.3编译阶段的事情,那么从哪里开始算是LLVM的后端?
优化器(Optimizer)和代码生成器(CodeGenerator)都可以算作LLVM后端
xcode是带有对IR代码是否进行优化的可视化界面的,一般情况下,Debug模式下默认都是没有开启代码优化,而Release模式下,则开启了优化
image.pngLLVM的优化级别
语法 | 释义 |
---|---|
O0 | None,不进行IR优化 |
O1 | Fast |
O2 | Faster |
O3 | Fastest |
Os | Fastest , Smallest |
Ofast | 比Os还要更近一步的优化 |
Oz | 让IR代码体积最小的优化 |
利用命令行对IR进行优化的举例
为了看到优化的IR代码,不直接利用xcode的优化了,利用终端的命令行对IR代码进行优化
clang -Os -S -fobjc-arc -emit-llvm main.m
bitCode的生成
// 先生成IR的main.ll文件
clang -S -fobjc-arc -emit-llvm main.m
// 利用main.ll文件生成main.bc文件
clang -emit-llvm -c main.ll -o main.bc
2.5 汇编
// .m格式的源文件转化为汇编代码
clang -S -fobjc-arc main.m -o main.s
// .ll格式的IR代码文件转化为汇编代码
clang -S -fobjc-arc main.ll -o main1.s
// .bc格式的bitCode优化后的文件转化为汇编代码
clang -S -fobjc-arc main.bc -o main2.s
// .m源码直接生成汇编的优化
clang -Os -S -fobjc-arc main.m -o main3.s
// IR生成汇编的优化
clang -Os -S -fobjc-arc main.ll -o main4.s
// bc生成汇编的优化
clang -Os -S -fobjc-arc main.bc -o main5.s
对比main.m未经过优化和经过优化分别生成的汇编main.s和main3.s :
image.png对比IR未经过优化和经过优化分别生成的汇编main1.s和main4.s
image.png对比bc未经过优化和经过优化分别生成的汇编main2.s和main5.s
image.png2.6 生成目标文件
目标文件的生成是汇编器以汇编代码
作为输入
,将汇编代码
转换成机器代码
,最后输出目标文件
(object file)
// 常用命令
clang -fmodules -c main.s -o main.o
查看目标文件main.o
的符号的命令
xcrun nm -nm main.o
image.png
-
undefined
: 表示在当前文件,暂时找不到某个符号,比如上图中找不到_printf
这个符号,也就是找不到printf
这个方法 -
external
: 表示这个符号是外部可以访问的. 比如上图中找不到的_printf
这个符号是可以在外部访问的到的,也就是说printf
这个方法不是本文件的方法,但是是可以经过外部的文件找得到的方法
2.7 生成可执行文件(链接)
链接器把编译生成的多个.o
文件.dylib
或.a
文件链接在一起生成一个可执行文件
,在iOS中这个可执行文件就是 mach-o
文件
用下述命令生成可执行文件:
clang main.o -o main
链接之后,我们再查看可执行文件的符号
查看可执行文件的符号的命令 :
xcrun nm -nm main
image.png
从图中可以看到,虽然undefined
标识是依然存在的,但是后面的括号中已经告诉我们_printf
符号是来自于libSystem
的
为什么要有这个from libSystem呢?
因为当这个可执行文件main要被执行的时候,main内部有一个符号_printf是来自于外部,当要调用这个_printf的时候,dyld会在加载的时候进行绑定,而如何绑定呢?就会根据符号提供的位置,也就是(from libSystem)来确定_printf符号是来自于libSystem的,这时iOS的操作系统中的libSystem动态库就会把_printf的地址告诉dyld,然后进行符号的绑定。
所以说,这个符号是在运行的时候动态绑定的。这也是为什么fishhook可以去hook一些外部函数的原因
2.8 执行可执行文件
当生成可执行文件main
之后,直接执行这个可执行文件 :
./main
image.png
查看一下可执行文件的基本信息 :
file main
image.png
可以看到main
这个可执行文件格式是Mach-O
,在64位
的x86架构
下可运行
网友评论