LLVM

作者: 我阿郑 | 来源:发表于2022-01-06 18:06 被阅读0次

C,C++,OC等语言,都是使用的编译器,生成相关的可执行文件, 与之对应的Python,Shell等脚本性语言,它们使用的是解释器, 解释器会在运行时解释执行代码,获取一段代码后就会将其翻译成目标代码(就是字节码(Bytecode)),然后一句一句地执行目标代码。但是跑起来之后可以不用重启启动编译,直接修改代码即可看到效果,类似热更新,可以帮我们缩短整个程序的开发周期和功能更新周期

总结来说:

  • 采用编译器生成机器码执行的好处是效率高,缺点是调试周期长
  • 解释器执行的好处是编写调试方便,缺点是执行效率低

随着移动应用的发展,很多客户要求编译库时用 libc++ 来代替 libstdc++.

libc++libstdc++这两个库有关系呢?

两个都是 C++ 标准库,libc++是针对 Clang 编译器特别重写的 C++ 标准库,而libstdc++ 则是 GCC 的对应 C++ 标准库了。

AndroidiOS都放弃了 GCC,全面转向Clang

一、简要的介绍一下 Clang

Clang 是一个 C、C++、Objective-C 和 Objective-C++ 编程语言的编译器前端,采用底层虚拟机(LLVM)作为后端.
ClangLLVM的一个子项目

至于为什么有了 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:后端负责将优化器优化后的中间代码转换为目标机器的代码,这一过程后端会最大化的利用目标机器的特殊指令,以提高代码的性能
image.png
  • 不同的前后端使用统一的中间代码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指针的retainrelease的调用
-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.通过不同的架构,生成对应的可执行文件

inputpreprocessorcompilerbackendassemblerlinkerbind-arch,这些关键词表示的是编译中的操作名称

main.m{0}.....{5},这些东西表示的是这一步操作中要读取的文件,也就是上一步操作的结果文件

objective-cobjective-c-cpp-outputirassemblerobjectimage 等关键词就是本步操作完成后,生成的文件,也就是上面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
image.png
  • BinaryOperator : 函数的第二个参数,这叫字节运算符。这一行表示这个字节运算符是加法运算。表明函数的第二个参数是一个加法运算的结果
image.png
  • 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.png
LLVM的优化级别
语法 释义
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.png

2.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架构下可运行

参考文章

深入剖析 iOS 编译 Clang LLVM
深入浅出iOS编译

LLVM和自定义LLVM插件
iOS用到的LLVM(二)

相关文章

  • LLVM

    LLVM 什么是LLVM? 官网:https://llvm.org/ The LLVM Project is a ...

  • iOS_LLVM

    LLVM 官网:https://llvm.org/[https://llvm.org/] The LLVM Pro...

  • iOS 逆向 day 18 GCC LLVM Clang

    一、LLVM 1. 什么是 LLVM 官网:https://llvm.org/ The LLVM Project ...

  • 浅谈LLVM

    何为LLVM 在LLVM的官网(https://llvm.org/[https://llvm.org/])中写到T...

  • iOS逆向-day10:LLVM 编译器

    一、LLVM的简单介绍 1.1、什么是LLVM官网:https://llvm.org/LLVM官网解释:The L...

  • 初识LLVM&Clang-开发Xcode插件

    初识LLVM&Clang-开发Xcode插件 LLVM Xcode现在使用的编译器就是LLVM。LLVM比以前使用...

  • LLVM 初始 ①

    什么是LLVM 官网:https://llvm.org/ The LLVM Project is a collec...

  • LLVM 工具

    llvm-dis llvm-dis 是一个反汇编工具,通过 LLVM 字节码文件(.bc)得到 LLVM 汇编文件...

  • LLVM(1)LLVM了解

    一、什么是LLVM 1、官网:https://llvm.org/The LLVM Project is a col...

  • llvm 编译

    1,下载llvm 源码git clone https://git.llvm.org/git/llvm.git/2,...

网友评论

      本文标题:LLVM

      本文链接:https://www.haomeiwen.com/subject/wkmhcrtx.html