iOS進(jìn)階02: Mach-O

一、什么是Mach-O文件?

Mach-OMach Object文件格式的縮寫,是 mac以及 iOS上可執(zhí)行文件的格式,對應(yīng)系統(tǒng)通過應(yīng)用二進(jìn)制接口(application binary interface,縮寫為ABI)來運(yùn)行該格式的的文件。Mach-O文件對應(yīng)有多種格式:

  • 目標(biāo)文件.o
  • 庫文件: .a靜態(tài)庫文件 .dylib動態(tài)庫文件 .framework文件,自己創(chuàng)建的為靜態(tài)庫文件
  • 可執(zhí)行文件
  • dyld動態(tài)鏈接器將依賴的動態(tài)庫加載到內(nèi)存
  • .dsym符號表

Mach-O格式用來替代BSD系統(tǒng)的a.out格式。Mach-O文件格式保存了在編譯過程鏈接過程中產(chǎn)生的機(jī)器代碼和數(shù)據(jù),從而為靜態(tài)鏈接和動態(tài)鏈接的代碼提供了單一文件格式。

clang單文件編譯

Xcode中我們可以直接創(chuàng)建.c文件,通過終端clang命令來對.c文件進(jìn)行編譯或生成可執(zhí)行文件,下面看一下clang怎樣使用的。

  • 1、創(chuàng)建一個(gè)main.c文件如下:
#include <stdio.h>
int main(){
    printf("打?。篽ello\n");
    return 0;
}
  • 2、編譯文件
clang -c main.c

會生成main.o文件,該文件即為mach-o文件,通過命令file main.o查看文件信息如下

main.o: Mach-O 64-bit object x86_64

是一個(gè)object類型的文件稱為目標(biāo)文件,并不是可執(zhí)行文件

  • 3、生成可執(zhí)行文件

    • 命令clang main.o 會生成a.out文件,即可執(zhí)行文件,通過ls查看
    • 命令clang -o main main.o 也會生成可執(zhí)行文件main
    • 命令clang -o main main.c 直接根據(jù)源文件生成可執(zhí)行文件main
    • 命令 size -x -l -m main 查看文件信息,如下:
    image
  • 4、執(zhí)行文件

./a.out 或 ./main
image

多個(gè)文件是如何編譯的呢?

開發(fā)中根據(jù)不同功能模塊我們會分很多文件來實(shí)現(xiàn),在clang中是可以對多個(gè)文件進(jìn)行一次性打包,生成一個(gè)可執(zhí)行文件。如下:

  • 1、新建一個(gè)功能文件 people.c
#include <stdio.h>
void sleep(){
    printf("正在睡覺\n");
}
  • 2、在main.c中聲明sleep方法并調(diào)用
void sleep();//聲明方法
int main(){
    printf("打?。篽ello\n");
    sleep();//調(diào)用方法
    return 0;
}
  • 3、編譯為可執(zhí)行文件
clang -o main main.c people.c
  • 4、執(zhí)行可執(zhí)行文件./main, 運(yùn)行結(jié)果如下:
    image

二、通用二進(jìn)制文件(Universal binary)

iOS中不同手機(jī)對應(yīng)著可能不同的架構(gòu),如arm64、armv7、armv7s。為了兼容不同架構(gòu)的手機(jī),蘋果推出了通用二進(jìn)制文件,包含了應(yīng)用程序常用的這些架構(gòu),因此通用二進(jìn)制文件,比單一架構(gòu)二進(jìn)制文件要大很多。

架構(gòu)選擇

image

通過以上配置真實(shí)編譯出來的是包含arm64、armv7架構(gòu)

通用二進(jìn)制文件在哪呢?

xxx.app中的xxx黑色文件即是通用二進(jìn)制文件,右鍵xxx.app顯示包內(nèi)容即可獲得。

lipo命令

通過lipo命令可以查看、拆分及合并以上提出的架構(gòu),在做靜態(tài)庫時(shí)也會使用,來合并真機(jī)下和模擬器下的靜態(tài)庫,以適應(yīng)不同的調(diào)試環(huán)境。

LoginApp.app中我獲取可執(zhí)行文件LoginApp

  • 1、查看架構(gòu)信息: lipo -info LoginApp
// 終端輸出結(jié)果如下
Architectures in the fat file: LoginApp are: armv7 arm64
  • 2、拆分armv7、arm64架構(gòu)
lipo LoginApp -thin armv7 -output LoginApp_armv7
lipo LoginApp -thin arm64 -output LoginApp_arm64
// 終端輸出結(jié)果如下
Non-fat file: LoginApp_armv7 is architecture: armv7
Non-fat file: LoginApp_arm64 is architecture: arm64
  • 3、合并架構(gòu)
lipo -create LoginApp_armv7 LoginApp_arm64 -output LoginApp_ALL
  • 產(chǎn)生的可執(zhí)行文件如圖:


    image

三、Mach-O文件結(jié)構(gòu)

官方圖解:


image

文件分為三個(gè)部分:

  • Header:包含Mach-O文件的基本信息,字節(jié)順序、架構(gòu)類型、加載指令的數(shù)量等
  • Load commands:包含區(qū)域位置、符號表、動態(tài)符號表,加載Mach-O文件時(shí)使用這里的數(shù)據(jù)確定內(nèi)存分布
  • Data:數(shù)據(jù)段segment,包含具體代碼、常量、類、方法等,有多個(gè)segment,每個(gè)segment有0到多個(gè)section,每個(gè)段有一個(gè)虛擬地址映射到進(jìn)程的地址空間

直接使用 MachOView打開LoginApp可執(zhí)行文件,如下:

image

image

結(jié)構(gòu)體對齊

1、查看Macho headers

// 查看 Header,包括 Load commands
objdump --macho --private-headers LoginApp
otool -l ${MACHO_PATH}
// 只查看 Header
objdump --macho --private-header LoginApp
otool -h ${MACHO_PATH}
image

image

Mach-O中定義了程序的入口main函數(shù)(定義在LC_MAIN的Load command命令),告訴動態(tài)鏈接器去加載程序的入口

// grep代表搜索, -A 5:向下輸出5行
objdump --macho --private-headers LoginApp | grep "MAIN" -A 5
image
  • 以上打印的兩段分別是armv7、arm64架構(gòu)下的header信息 在objc4源碼loader.h文件中有mach_header的結(jié)構(gòu)體定義,如下:
struct mach_header {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
};
struct mach_header_64 {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
    uint32_t    reserved;   /* reserved */
};
  • magic:魔數(shù),確定是64位還是32位
  • cputypecpu類型
  • cpusubtypecpu子類型
  • filetypeMach-O支持多種文件類型,使用filetype來標(biāo)注具體文件類型
  • ncmds:加載命令的數(shù)量
  • sizeofcmds:命令區(qū)域(load commands)總的字節(jié)大小
  • flags:標(biāo)識二進(jìn)制文件所支持的功能,主要與系統(tǒng)的加載、鏈接有關(guān)

2、Load commands

Header之后是load commands段為加載命令段,在header結(jié)構(gòu)體中有對加載命令段相關(guān)信息的描述,用于解析加載命令。在objc4源碼loader.h中,有對loadcommand的定義:

struct load_command {
    uint32_t cmd;       /* type of load command */
    uint32_t cmdsize;   /* total size of command in bytes */
};
  • cmd:命令類型,針對不同架構(gòu)有不同的結(jié)構(gòu)(32位、64位)
  • cmdsize:命令所占字節(jié)大?。?2位size必須為4字節(jié)的倍數(shù),64位size必須為8字節(jié)的倍數(shù))在文件中有兩個(gè)結(jié)構(gòu)體segment_commandsegment_command_64針對不同架構(gòu)的結(jié)構(gòu)體,內(nèi)部設(shè)置字段相同。以segment_command_64為例:
struct segment_command_64 { /* for 64-bit architectures */
    uint32_t    cmd;        /* LC_SEGMENT_64 */
    uint32_t    cmdsize;    /* includes sizeof section_64 structs */
    char        segname[16];    /* segment name */
    uint64_t    vmaddr;     /* memory address of this segment */
    uint64_t    vmsize;     /* memory size of this segment */
    uint64_t    fileoff;    /* file offset of this segment */
    uint64_t    filesize;   /* amount to map from the file */
    vm_prot_t   maxprot;    /* maximum VM protection */
    vm_prot_t   initprot;   /* initial VM protection */
    uint32_t    nsects;     /* number of sections in segment */
    uint32_t    flags;      /* flags */
};
  • cmd:加載命令類型
  • LC_SEGMENT:表示這好似一個(gè)段加載命令,需要將它加載到對應(yīng)的進(jìn)程空間上
  • LC_LOAD_DYLIB:這是一個(gè)需要動態(tài)加載的鏈接庫,它使用dylib_command結(jié)構(gòu)體表示
  • LC_MAIN:記錄了可執(zhí)行文件的主函數(shù)main()的位置,它使用entry_point_command結(jié)構(gòu)體表示
  • LC_CODE_SIGNATURE:代碼簽名的加載命令,描述了Mach-O的代碼簽名信息,它屬于鏈接信息,使用linkedit_data_command結(jié)構(gòu)體表示
  • cmdsize:加載命令所占內(nèi)存大小
  • segname:存放16字節(jié)大小的段名字,當(dāng)前是__PAGEZERO
  • vmaddr:段的虛擬內(nèi)存起始地址
  • vmsize:段的虛擬內(nèi)存大小
  • fileoff:段在文件中偏移量
  • filesize:段在文件大小
  • maxprot:段頁面所需要的最高內(nèi)存保護(hù)(4=r,2=w,1=x)
  • initprot:段頁面初始的內(nèi)存保護(hù)
  • nsects:段中包含section的數(shù)量
  • flags:其他雜項(xiàng)標(biāo)志位

以上加載命令含義如下:

  • LC_SEGMENT_64:將文件中的段映射到進(jìn)程地址空間中
  • LC_DYLD_INFO_ONLY:動態(tài)鏈接相關(guān)信息
  • LC_SYMTAB:符號表信息,位置、偏移、數(shù)據(jù)個(gè)數(shù),供dyld使用
  • LC_DYSYMTAB:動態(tài)符號表信息,供dyld使用
  • LC_LOAD_DYLINKER:鏈接器信息,記錄使用那些鏈接器完成內(nèi)核后序的加載工作
  • LC_UUID:Mach-O文件的唯一標(biāo)識
  • LC_VERSION_MIN_MACOSX:支持最低操作系統(tǒng)版本
  • LC_SOURCE_VERSION:源代碼的版本號
  • LC_MAIN:設(shè)置主線程的入口
  • LC_LOAD_DYLIB:依賴庫信息,dyld通過該命令去加載依賴庫
  • LC_FUNCTION_STARTS:函數(shù)的起始地址表
  • LC_CODE_SIGNATURE:代碼簽名

3、Data

Data區(qū)域由Segment段和Section節(jié)組成:

  • segment主要有__TEXT__DATA組成

  • __text:是主程序代碼

    • __stubs__stub_helper:是動態(tài)鏈接的樁
    • __cstring:程序中c語言字符串
    • __const:常量
  • __DATA含義:

    • Section64(__TEXT,__objc_methname):OC類名
    • Section64(__DATA,__objc_classlist):OC類列表
    • Section64(__DATA,__objc_protollist):OC原型列表
    • Section64(__DATA,__objc_imageinfo):OC鏡像信息
    • Section64(__DATA,__objc_selfrefs):OC類自引用
    • Section64(__DATA,__objc_superrefs):OC類超類的引用
    • Section64(__DATA,__ivar):OC類成員變量

總結(jié)

  • 查看mach-header
    • objdump --macho -private-header ${MACHO_PATH}
    • otool -h ${MACHO_PATH}
  • 查看__TEXT: objdump --macho -d ${MACHO_PATH}
  • 查看符號表: objdump --macho --syms ${MACHO_PATH}
  • 查看導(dǎo)出符號表: objdump --macho --exports-trie ${MACHO_PATH}
  • 查看間接符號表: objdump --macho --indirect-symbols ${MACHO_PATH}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • # 動態(tài)鏈接 動態(tài)鏈接的基本思想是把程序按照模塊拆分成各個(gè)相對獨(dú)立部分,在程序運(yùn)行時(shí)才將它們鏈接在一起形成一個(gè)完整...
    Tenloy閱讀 4,149評論 3 5
  • 什么是Mach-O文件? Mach-O文件是Mach object文件的縮寫,它在NeXTSTEP.MacOS,i...
    SharaYuki閱讀 4,418評論 3 13
  • 上一篇說到源碼經(jīng)過預(yù)處理、編譯、匯編之后生成目標(biāo)文件,這一章介紹一下iOS、Mac OS中目標(biāo)文件的格式Mach-...
    Tenloy閱讀 2,227評論 2 9
  • 熟悉Linux和windows開發(fā)的同學(xué)都知道,ELF是Linux下可執(zhí)行文件的格式,PE32/PE32+是win...
    Klaus_J閱讀 4,125評論 1 10
  • 推薦指數(shù): 6.0 書籍主旨關(guān)鍵詞:特權(quán)、焦點(diǎn)、注意力、語言聯(lián)想、情景聯(lián)想 觀點(diǎn): 1.統(tǒng)計(jì)學(xué)現(xiàn)在叫數(shù)據(jù)分析,社會...
    Jenaral閱讀 6,037評論 0 5

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