iOS逆向04 -- 編譯過程

編譯器的組成部分

傳統(tǒng)的編譯器通常分為三個部分,分別為:前端(frontEnd)優(yōu)化器(Optimizer)后端(backEnd),在編譯過程中,各自執(zhí)行不同的功能:

  • 前端(frontEnd)主要負責詞法分析,語法分析和語義分析,將源代碼轉化為抽象語法樹(AST),最后生成中間代碼;
  • 優(yōu)化器(Optimizer)則是在前端的基礎上,對生成的中間代碼進行優(yōu)化;
  • 后端(backEnd)則是將已經(jīng)優(yōu)化的中間代碼,根據(jù)不同平臺架構轉化為各自平臺的機器代碼;

編譯器的種類

GCC
  • GCC(GNU Compiler Collection,GNU編譯器套裝),是一套由 GNU 開發(fā)的編程語言編譯器,GCC 原名為 GNU C 語言編譯器,因為它原本只能處理 C語言,GCC 快速演進,變得可處理 C++、Fortran、Pascal、Objective-C、Java, 以及 Ada 等他語言;
LLVM
  • LLVM是一個完整的編譯器(compiler)框架系統(tǒng),以C++編寫而成,用于優(yōu)化以任意程序語言編寫的程序的編譯時間(compile-time)、鏈接時間(link-time)、運行時間(run-time)以及空閑時間(idle-time),對開發(fā)者保持開放,并兼容已有腳本;
  • 在理解LLVM時,我們可以認為它包括了一個狹義的LLVM和一個廣義的LLVM;
  • 廣義的LLVM其實就是指整個LLVM編譯器架構,包括了前端、后端、優(yōu)化器、眾多的庫函數(shù)以及其他的模塊;
  • 狹義的LLVM其實就是聚焦于編譯器后端功能(代碼生成、代碼優(yōu)化、JIT等)的一系列模塊和庫;
Clang
  • Clang是LLVM編譯系統(tǒng)的前端,是GCC的替代品,其可以看成是LLVM的子集,相比于GCC編譯器Clang功能更加強大;
  • 其速度快,占用內(nèi)存小,診斷信息可讀性強,兼容性好,Clang有靜態(tài)分析而GCC沒有,Clang使用BSD許可證,GCC使用GPL許可證;
  • 下面用一張圖來表示Clang與LLVM之間的關系:
Snip20210112_43.png
  • 從圖中可以看出Clang其實大致上可以看成是LLVM編譯器架構的前端,主要處理一些和具體機器無關的針對語言的分析操作;編譯器的優(yōu)化器部分和后端部分其實就是我們之前談到的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到指定文件路徑,然后執(zhí)行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到指定文件路徑,執(zhí)行詞法分析命令: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),且會對語法樹進行代碼靜態(tài)分析,校驗語法是否錯誤;
  • 終端執(zhí)行語法分析命令:clang -Xclang -ast-dump -fsyntax-only main.m,終端輸出結果如下所示:
Snip20210112_45.png
第四步:生成中間代碼文件
  • 終端執(zhí)行命令:clang -O3 -S -emit-llvm main.m -o main.ll,本地生成一個main.ll文件,用Xcode打開其內(nèi)容如下:
; 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)"}
第五步:優(yōu)化器(Optimizer) 優(yōu)化中間代碼
  • 全局變量優(yōu)化、循環(huán)優(yōu)化、尾遞歸優(yōu)化等,如果開啟了 bitcode 蘋果會做進一步的優(yōu)化;
第六步:生成匯編文件
  • 終端執(zhí)行命令:clang -S -fobjc-arc main.m -o main.s,本地生成一個main.s文件,用Xcode打開其內(nèi)容如下:
    .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
  • 終端執(zhí)行命令:xcrun clang -fmodules -c main.m -o main.o,本地生成一個main.o文件;
第八步:鏈接器 鏈接多個目標文件最終生成一個可執(zhí)行的Mach-O文件
  • 鏈接器把編譯生成的所有 .o 文件和(dylib、a、tbd)文件結合,生成一個Mach-O文件(可執(zhí)行文件);
  • 使用終端命令:xcrun clang main.o -o main;
  • 在編譯時,鏈接器的主要工作任務:
    • 符號綁定:完成變量名與其地址,函數(shù)名與其地址之間的綁定;
    • Mach-O文件中的主要內(nèi)容是數(shù)據(jù)和代碼,數(shù)據(jù)就是全局變量,代碼就是函數(shù), 全局變量和函數(shù)都存儲在指定的位置,計算機指令通過符號去操作數(shù)據(jù)和調(diào)用函數(shù),首先必須要進行符號的綁定,才能根據(jù)符號去操作數(shù)據(jù)和調(diào)用函數(shù);
    • 靜態(tài)庫的鏈接:將靜態(tài)庫文件直接打包進入Mach-O文件中;
    • 生成的多個Mach-O文件最終合成一個Mach-O文件,項目中各個文件之間的函數(shù)存在相互調(diào)用,也就是說存在相互依賴,單個的Mach-O文件是無法執(zhí)行成功的,因為當前文件中需要調(diào)用其他文件中的函數(shù)方法,最終由于找不到對應的函數(shù)而終止執(zhí)行;所以我們需要把所有的Mach-O文件合并,最終生成一個可執(zhí)行的Mach-O文件;
  • 對于Mach-O文件中的動態(tài)庫,在編譯期只是對其進行引用并沒有加載,等到了App運行時,dyld會對Mach-O文件中所有動態(tài)庫的引用進行掃描,逐一加載鏈接,但不會打包進Mach-O文件中,這是與靜態(tài)庫最大的區(qū)別;

編譯多個文件 -- 實戰(zhàn)演練

  • 首先創(chuàng)建一個C語言工程如下所示:
Snip20210118_13.png
  • 在終端依次輸入:
    • xcrun clang -c main.m
    • xcrun clang -c YYPerson.m
  • 在本地會生成兩個目標文件 main.o 與YYPerson.o;
Snip20210118_14.png
  • 將多個編譯之后的目標文件通過鏈接器進行鏈接,生成a.out可執(zhí)行文件;
  • 在終端中輸入: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 動態(tài)庫做鏈接處理時,鏈接器會嘗試解析所有的undefined 符號;
  • dylib 這種格式,表示是動態(tài)鏈接的,編譯的時候不會被編譯到執(zhí)行文件中,在程序執(zhí)行的時候才 link,這樣就不用算到包大小里,而且不更新執(zhí)行程序就能夠更新庫;
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 引言 維基百科:編譯語言(英語:Compiled language)是一種以編譯器來實現(xiàn)的編程語言。它不像解釋型語...
    Flame_Dream閱讀 8,723評論 5 52
  • 前言 我們知道,編程語言分為編譯語言和解釋語言。兩者的執(zhí)行過程不同。 編譯語言是通過編譯器將代碼直接編寫成機器碼,...
    迷路卜閱讀 2,016評論 1 5
  • 編譯器 iOS編譯和打包時,編譯器直接將代碼編譯成機器碼,然后直接在CPU上運行。而不用使用解釋器運行代碼。因為這...
    shawnr閱讀 7,293評論 1 22
  • iOS 開發(fā)中 Objective-C 是 Clang / LLVM 來編譯的。swift 是 Swift / L...
    forping閱讀 1,152評論 0 0
  • 前言 語言類型 我們有很多維度可以將計算機語言進行分類,其中以編譯/執(zhí)行方式為維度,可以將計算機語言分為: 編譯型...
    AiLearn閱讀 2,636評論 1 6

友情鏈接更多精彩內(nèi)容