iOS逆向04 -- 编译过程

编译器的组成部分

传统的编译器通常分为三个部分,分别为:前端(frontEnd)优化器(Optimizer)后端(backEnd),在编译过程中,各自执行不同的功能:

  • 前端(frontEnd)主要负责词法分析,语法分析和语义分析,将源代码转化为抽象语法树(AST),最后生成中间代码;
  • 优化器(Optimizer)则是在前端的基础上,对生成的中间代码进行优化;
  • 后端(backEnd)则是将已经优化的中间代码,根据不同平台架构转化为各自平台的机器代码;

编译器的种类

GCC
  • GCC(GNU Compiler Collection,GNU编译器套装),是一套由 GNU 开发的编程语言编译器,GCC 原名为 GNU C 语言编译器,因为它原本只能处理 C语言,GCC 快速演进,变得可处理 C++、Fortran、Pascal、Objective-C、Java, 以及 Ada 等他语言;
LLVM
  • LLVM是一个完整的编译器(compiler)框架系统,以C++编写而成,用于优化以任意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time),对开发者保持开放,并兼容已有脚本;
  • 在理解LLVM时,我们可以认为它包括了一个狭义的LLVM和一个广义的LLVM;
  • 广义的LLVM其实就是指整个LLVM编译器架构,包括了前端、后端、优化器、众多的库函数以及其他的模块;
  • 狭义的LLVM其实就是聚焦于编译器后端功能(代码生成、代码优化、JIT等)的一系列模块和库;
Clang
  • Clang是LLVM编译系统的前端,是GCC的替代品,其可以看成是LLVM的子集,相比于GCC编译器Clang功能更加强大;
  • 其速度快,占用内存小,诊断信息可读性强,兼容性好,Clang有静态分析而GCC没有,Clang使用BSD许可证,GCC使用GPL许可证;
  • 下面用一张图来表示Clang与LLVM之间的关系:
Snip20210112_43.png
  • 从图中可以看出Clang其实大致上可以看成是LLVM编译器架构的前端,主要处理一些和具体机器无关的针对语言的分析操作;编译器的优化器部分和后端部分其实就是我们之前谈到的LLVM后端(狭义的LLVM),而整体的Compiler架构就是LLVM架构;

iOS编译过程

  • Xcode的默认使用的编译器是 clang,clang首先会对 Objective-C 代码做预处理,分析检查,然后将其转换为低级的类汇编代码;
  • 下面通过具体的代码来演示iOS的编译过程:
第一步:预处理(preprocessor)
  • C语言代码如下所示:
#import <Foundation/Foundation.h>

#define DEBUG 1

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //insert code ...
        #ifdef DEBUG
          printf("hello debug\n");
        #else
          printf("hello world\n");
        #endif
          NSLog(@"Hello, World!");
    }
    return 0;
}
  • 终端cd到指定文件路径,然后执行xcrun clang -E main.m,最后终端输出的代码如下所示:
int main(int argc, const char * argv[]) {
    @autoreleasepool {

          printf("hello debug\n");

          NSLog(@"Hello, World!");
    }
    return 0;
}
  • 可以看到,在代码预处理的时候,注释被删除,条件编译指令被处理
  • 代码预处理完成之后的具体编译流程如下图所示:
Snip20210112_44.png
第二步:词法分析
  • 词法分析:通过词法分析器读入源文件的字符流,然后将他们组织成有意义的词素(lexeme)序列,对于每个词素,词法分析器会生成对应的词法单元(token)作为输出,测试代码如下:
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        int b = 20;
        int c = a + b;
        NSLog(@" c = %d",c);
    }
    return 0;
}
  • 终端cd到指定文件路径,执行词法分析命令:xcrun clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m 最后终端输出的代码如下所示:
annot_module_include '#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        int b'      Loc=<main.m:9:1>
int 'int'    [StartOfLine]  Loc=<main.m:11:1>
identifier 'main'    [LeadingSpace] Loc=<main.m:11:5>
l_paren '('     Loc=<main.m:11:9>
int 'int'       Loc=<main.m:11:10>
identifier 'argc'    [LeadingSpace] Loc=<main.m:11:14>
comma ','       Loc=<main.m:11:18>
const 'const'    [LeadingSpace] Loc=<main.m:11:20>
char 'char'  [LeadingSpace] Loc=<main.m:11:26>
star '*'     [LeadingSpace] Loc=<main.m:11:31>
identifier 'argv'    [LeadingSpace] Loc=<main.m:11:33>
l_square '['        Loc=<main.m:11:37>
r_square ']'        Loc=<main.m:11:38>
r_paren ')'     Loc=<main.m:11:39>
l_brace '{'  [LeadingSpace] Loc=<main.m:11:41>
at '@'   [StartOfLine] [LeadingSpace]   Loc=<main.m:12:5>
identifier 'autoreleasepool'        Loc=<main.m:12:6>
l_brace '{'  [LeadingSpace] Loc=<main.m:12:22>
int 'int'    [StartOfLine] [LeadingSpace]   Loc=<main.m:13:9>
identifier 'a'   [LeadingSpace] Loc=<main.m:13:13>
equal '='    [LeadingSpace] Loc=<main.m:13:15>
numeric_constant '10'    [LeadingSpace] Loc=<main.m:13:17>
semi ';'        Loc=<main.m:13:19>
int 'int'    [StartOfLine] [LeadingSpace]   Loc=<main.m:14:9>
identifier 'b'   [LeadingSpace] Loc=<main.m:14:13>
equal '='    [LeadingSpace] Loc=<main.m:14:15>
numeric_constant '20'    [LeadingSpace] Loc=<main.m:14:17>
semi ';'        Loc=<main.m:14:19>
int 'int'    [StartOfLine] [LeadingSpace]   Loc=<main.m:15:9>
identifier 'c'   [LeadingSpace] Loc=<main.m:15:13>
equal '='    [LeadingSpace] Loc=<main.m:15:15>
identifier 'a'   [LeadingSpace] Loc=<main.m:15:17>
plus '+'     [LeadingSpace] Loc=<main.m:15:19>
identifier 'b'   [LeadingSpace] Loc=<main.m:15:21>
semi ';'        Loc=<main.m:15:22>
identifier 'NSLog'   [StartOfLine] [LeadingSpace]   Loc=<main.m:16:9>
l_paren '('     Loc=<main.m:16:14>
at '@'      Loc=<main.m:16:15>
string_literal '" c = %d"'      Loc=<main.m:16:16>
comma ','       Loc=<main.m:16:25>
identifier 'c'      Loc=<main.m:16:26>
r_paren ')'     Loc=<main.m:16:27>
semi ';'        Loc=<main.m:16:28>
r_brace '}'  [StartOfLine] [LeadingSpace]   Loc=<main.m:17:5>
return 'return'  [StartOfLine] [LeadingSpace]   Loc=<main.m:18:5>
numeric_constant '0'     [LeadingSpace] Loc=<main.m:18:12>
semi ';'        Loc=<main.m:18:13>
r_brace '}'  [StartOfLine]  Loc=<main.m:19:1>
eof ''      Loc=<main.m:20:1>
  • 看到词法分析器将代码中的关键字,操作符,变量,分号,括号全部分割开来成为独立的词法单元
第三步:语法分析
  • 词法分析获取的词法单元(Token)流,会被解析成一棵抽象语法树(abstract syntax tree - AST),且会对语法树进行代码静态分析,校验语法是否错误;
  • 终端执行语法分析命令:clang -Xclang -ast-dump -fsyntax-only main.m,终端输出结果如下所示:
Snip20210112_45.png
第四步:生成中间代码文件
  • 终端执行命令:clang -O3 -S -emit-llvm main.m -o main.ll,本地生成一个main.ll文件,用Xcode打开其内容如下:
; ModuleID = 'main.m'
source_filename = "main.m"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.15.0"

%struct.__NSConstantString_tag = type { i32*, i32, i8*, i64 }

@__CFConstantStringClassReference = external global [0 x i32]
@.str = private unnamed_addr constant [8 x i8] c" c = %d\00", section "__TEXT,__cstring,cstring_literals", align 1
@_unnamed_cfstring_ = private global %struct.__NSConstantString_tag { i32* getelementptr inbounds ([0 x i32], [0 x i32]* @__CFConstantStringClassReference, i32 0, i32 0), i32 1992, i8* getelementptr inbounds ([8 x i8], [8 x i8]* @.str, i32 0, i32 0), i64 7 }, section "__DATA,__cfstring", align 8

; Function Attrs: ssp uwtable
define i32 @main(i32, i8** nocapture readnone) local_unnamed_addr #0 {
  %3 = tail call i8* @llvm.objc.autoreleasePoolPush() #1
  notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*), i32 30)
  tail call void @llvm.objc.autoreleasePoolPop(i8* %3)
  ret i32 0
}

; Function Attrs: nounwind
declare i8* @llvm.objc.autoreleasePoolPush() #1

declare void @NSLog(i8*, ...) local_unnamed_addr #2

; Function Attrs: nounwind
declare void @llvm.objc.autoreleasePoolPop(i8*) #1

attributes #0 = { ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "darwin-stkchk-strong-link" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "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,+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" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "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,+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", [2 x i32] [i32 10, i32 15]}
!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 11.0.0 (clang-1100.0.33.17)"}
第五步:优化器(Optimizer) 优化中间代码
  • 全局变量优化、循环优化、尾递归优化等,如果开启了 bitcode 苹果会做进一步的优化;
第六步:生成汇编文件
  • 终端执行命令:clang -S -fobjc-arc main.m -o main.s,本地生成一个main.s文件,用Xcode打开其内容如下:
    .section    __TEXT,__text,regular,pure_instructions
    .build_version macos, 10, 15    sdk_version 10, 15
    .globl  _main                   ## -- Begin function main
    .p2align    4, 0x90
_main:                                  ## @main
    .cfi_startproc
## %bb.0:
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    subq    $48, %rsp
    movl    $0, -4(%rbp)
    movl    %edi, -8(%rbp)
    movq    %rsi, -16(%rbp)
    callq   _objc_autoreleasePoolPush
    leaq    L__unnamed_cfstring_(%rip), %rsi
    movl    $10, -20(%rbp)
    movl    $20, -24(%rbp)
    movl    -20(%rbp), %edi
    addl    -24(%rbp), %edi
    movl    %edi, -28(%rbp)
    movl    -28(%rbp), %edi
    movl    %edi, -32(%rbp)         ## 4-byte Spill
    movq    %rsi, %rdi
    movl    -32(%rbp), %esi         ## 4-byte Reload
    movq    %rax, -40(%rbp)         ## 8-byte Spill
    movb    $0, %al
    callq   _NSLog
    movq    -40(%rbp), %rdi         ## 8-byte Reload
    callq   _objc_autoreleasePoolPop
    xorl    %eax, %eax
    addq    $48, %rsp
    popq    %rbp
    retq
    .cfi_endproc
                                        ## -- End function
    .section    __TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
    .asciz  " c = %d"

    .section    __DATA,__cfstring
    .p2align    3               ## @_unnamed_cfstring_
L__unnamed_cfstring_:
    .quad   ___CFConstantStringClassReference
    .long   1992                    ## 0x7c8
    .space  4
    .quad   L_.str
    .quad   7                       ## 0x7

    .section    __DATA,__objc_imageinfo,regular,no_dead_strip
L_OBJC_IMAGE_INFO:
    .long   0
    .long   64

.subsections_via_symbols
第七步:生成目标文件(二进制文件) main.o
  • 终端执行命令:xcrun clang -fmodules -c main.m -o main.o,本地生成一个main.o文件;
第八步:链接器 链接多个目标文件最终生成一个可执行的Mach-O文件
  • 链接器把编译生成的所有 .o 文件和(dylib、a、tbd)文件结合,生成一个Mach-O文件(可执行文件);
  • 使用终端命令:xcrun clang main.o -o main
  • 在编译时,链接器的主要工作任务:
    • 符号绑定:完成变量名与其地址,函数名与其地址之间的绑定;
    • Mach-O文件中的主要内容是数据和代码,数据就是全局变量,代码就是函数, 全局变量和函数都存储在指定的位置,计算机指令通过符号去操作数据和调用函数,首先必须要进行符号的绑定,才能根据符号去操作数据和调用函数;
    • 静态库的链接:将静态库文件直接打包进入Mach-O文件中;
    • 生成的多个Mach-O文件最终合成一个Mach-O文件,项目中各个文件之间的函数存在相互调用,也就是说存在相互依赖,单个的Mach-O文件是无法执行成功的,因为当前文件中需要调用其他文件中的函数方法,最终由于找不到对应的函数而终止执行;所以我们需要把所有的Mach-O文件合并,最终生成一个可执行的Mach-O文件;
  • 对于Mach-O文件中的动态库,在编译期只是对其进行引用并没有加载,等到了App运行时,dyld会对Mach-O文件中所有动态库的引用进行扫描,逐一加载链接,但不会打包进Mach-O文件中,这是与静态库最大的区别;

编译多个文件 -- 实战演练

  • 首先创建一个C语言工程如下所示:
Snip20210118_13.png
  • 在终端依次输入:
    • xcrun clang -c main.m
    • xcrun clang -c YYPerson.m
  • 在本地会生成两个目标文件 main.o 与YYPerson.o;
Snip20210118_14.png
  • 将多个编译之后的目标文件通过链接器进行链接,生成a.out可执行文件;
  • 在终端中输入:xcrun clang main.o YYPerson.o -Wl,xcrun —show-sdk-path/System/Library/Frameworks/Foundation.framework/Foundation
Snip20210118_15.png
  • 在终端中输入以下命令进行查看:xcrun nm -nm a.out
Snip20210118_16.png
  • undefined 符号表示的是该文件类未定义,所以在目标文件和 Foundation framework 动态库做链接处理时,链接器会尝试解析所有的undefined 符号;
  • dylib 这种格式,表示是动态链接的,编译的时候不会被编译到执行文件中,在程序执行的时候才 link,这样就不用算到包大小里,而且不更新执行程序就能够更新库;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容

  • 引言 维基百科:编译语言(英语:Compiled language)是一种以编译器来实现的编程语言。它不像解释型语...
    Flame_Dream阅读 8,562评论 5 52
  • 前言 我们知道,编程语言分为编译语言和解释语言。两者的执行过程不同。 编译语言是通过编译器将代码直接编写成机器码,...
    迷路卜阅读 1,909评论 1 5
  • 编译器 iOS编译和打包时,编译器直接将代码编译成机器码,然后直接在CPU上运行。而不用使用解释器运行代码。因为这...
    shawnr阅读 6,840评论 1 22
  • iOS 开发中 Objective-C 是 Clang / LLVM 来编译的。swift 是 Swift / L...
    forping阅读 970评论 0 0
  • 前言 语言类型 我们有很多维度可以将计算机语言进行分类,其中以编译/执行方式为维度,可以将计算机语言分为: 编译型...
    AiLearn阅读 2,399评论 1 6