iOS中Mach-O概覽

希望通過本文來記錄對(duì)于iOS開發(fā)對(duì)Mach-O需要有的基本了解。

蘋果推出Mach-O的背景:

  1. 過渡至基于 Mach 內(nèi)核的操作系統(tǒng):蘋果于 2001 年推出了 macOS(當(dāng)時(shí)稱為 Mac OS X)操作系統(tǒng),該操作系統(tǒng)采用了基于 Mach 內(nèi)核的架構(gòu)。為了適應(yīng)新的操作系統(tǒng)架構(gòu),蘋果需要引入一種新的文件格式來支持該架構(gòu),用于可執(zhí)行文件、靜態(tài)庫和動(dòng)態(tài)庫的存儲(chǔ)和交互。
  2. 提高性能和可擴(kuò)展性:Mach-O 文件格式相對(duì)于舊的目標(biāo)文件格式(如 a.out 格式)具有更好的性能和可擴(kuò)展性。它采用了更加緊湊和高效的數(shù)據(jù)結(jié)構(gòu),使得應(yīng)用程序的加載、鏈接和執(zhí)行更高效。這在日益復(fù)雜的應(yīng)用程序和需求下變得尤為重要。
  3. 支持 Objective-C 和 Cocoa 框架:蘋果廣泛采用 Objective-C 編程語言和 Cocoa 框架來開發(fā) macOS 和 iOS 上的應(yīng)用程序。Mach-O 文件格式與 Objective-C 運(yùn)行時(shí)和 Cocoa 框架的集成緊密相關(guān),使得開發(fā)者可以更好地利用這些技術(shù)進(jìn)行應(yīng)用程序開發(fā)。
  4. 跨平臺(tái)支持和移植性:Mach-O 文件格式不僅用于 macOS 和 iOS,還可以支持其他基于 Darwin 內(nèi)核的操作系統(tǒng),如 tvOS 和 watchOS。這種一致的文件格式使得開發(fā)者可以更方便地在不同的蘋果平臺(tái)上共享和移植代碼,提高開發(fā)效率和代碼復(fù)用性。
  5. 操作系統(tǒng)集成:Mach-O 文件格式與蘋果操作系統(tǒng)的內(nèi)核(XNU)緊密集成。蘋果控制了 Mach-O 格式的規(guī)范和解析器,從而使得操作系統(tǒng)和應(yīng)用程序可以更緊密地進(jìn)行交互和整合。

一、認(rèn)識(shí)Mach-O

Xcode工程中,我們可以看到編譯設(shè)置里面有一個(gè)Mach-O type, 可以看到主工程的格式是Executable(可執(zhí)行文件)。

可執(zhí)行文件.png

而在組件化工程里,有一些本地或私有庫我們可能會(huì)在podspec中聲明s.static_framework = true,這樣就會(huì)是靜態(tài)庫;三方庫沒有這個(gè)聲明默認(rèn)是動(dòng)態(tài)庫。

靜態(tài)庫 動(dòng)態(tài)庫
假設(shè)本地庫Home模塊.png
AFNetworking.png

Mach-OMach Object的縮寫,它是Mac/iOS 中用于存儲(chǔ)程序、庫的標(biāo)準(zhǔn)格式。作為 a.out 格式的替代,Mach-O提供了更強(qiáng)的擴(kuò)展性,并提升了符號(hào)表中信息的訪問速度。

類型 代表文件
Executable(可執(zhí)行文件) xxx.app/xxx、推送擴(kuò)展
Dynamic Library(動(dòng)態(tài)庫文件) .dylib(一般是系統(tǒng)動(dòng)態(tài)庫)和 xxx.framework/xxx (三方動(dòng)態(tài)庫)
Bundle 一種特定結(jié)構(gòu)的文件夾,可以包含可執(zhí)行文件、動(dòng)態(tài)庫、靜態(tài)庫和各種資源文件,以及配置文件等,通常作為插件或擴(kuò)展。需通過dlopen加載。
Static Library 靜態(tài)庫文件(.a文件,是多個(gè).o文件的集合),如pod庫聲明s.static_framework = true,產(chǎn)物是靜態(tài)框架
Relocatable Object File 目標(biāo)文件(.o文件,編譯源代碼得到的中間文件)

二、Mach-O的類型

1. 有哪些類型

我們可以在Xcode.app查看到Mach-O的類型定義如下,路徑為:
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/mach-o/loader.h

#define MH_OBJECT   0x1     /* relocatable object file */
#define MH_EXECUTE  0x2     /* demand paged executable file */
#define MH_FVMLIB   0x3     /* fixed VM shared library file */
#define MH_CORE     0x4     /* core file */
#define MH_PRELOAD  0x5     /* preloaded executable file */
#define MH_DYLIB    0x6     /* dynamically bound shared library */
#define MH_DYLINKER 0x7     /* dynamic link editor */
#define MH_BUNDLE   0x8     /* dynamically bound bundle file */
#define MH_DYLIB_STUB   0x9     /* shared library stub for static linking only, no section contents */
#define MH_DSYM     0xa     /* companion file with only debug  sections */
#define MH_KEXT_BUNDLE  0xb     /* x86_64 kexts */
#define MH_FILESET  0xc     /* a file composed of other Mach-Os to be run in the same userspace sharing a single linkedit. */
#define MH_GPU_EXECUTE  0xd     /* gpu program */
#define MH_GPU_DYLIB    0xe     /* gpu support functions */

2. 常見的Mach-O類型

MH_OBJECT:目標(biāo)文件即 .o 文件 以及靜態(tài)庫文件即 .a 文件(多個(gè).o文件合并在一起);
MH_EXECUTE:可執(zhí)行文件,即App編譯運(yùn)行后生成的可執(zhí)行文件,在/Products路徑下;
MH_DYLIB:動(dòng)態(tài)庫文件,即.dylib文件 或者 .framework文件;
MH_DYLINKER:/usr/lib/dyld路徑下的dyld文件;
MH_DSYM:Xcode打包后生成的符號(hào)表文件,即.dSYM文件;

3. 如何查看mach-o文件類型

找到我們的APP后可通過命令行查看類型。

  • 通過file命令
// 1.查看APP的可執(zhí)行文件
file xxx.app/xxx
輸出: Mach-O 64-bit executable arm64
// 2.查看pod三方庫的machO
file xxx.app/Frameworks/AFNetworking.framework/AFNetworking
輸出:Mach-O 64-bit dynamically linked shared library arm64

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

項(xiàng)目 內(nèi)容
結(jié)構(gòu)圖
Mach-O文件結(jié)構(gòu).png
Header 包含Mach-O文件的基本信息,例如文件類型,支持的CPU架構(gòu)類型,加載指令的數(shù)量,所占內(nèi)存大小等
Load Command 不同數(shù)據(jù)段segment的加載命令,指導(dǎo)加載器加載數(shù)據(jù)
Data 在Load Command中定義的Segment的原始數(shù)據(jù)。

四、查看Mach-O的方式

otool -l <file>:顯示 Mach-O 文件的加載命令信息。
otool -t <file>:顯示 Mach-O 文件的文本節(jié)信息。
otool -L <file>:顯示 Mach-O 文件的依賴庫信息。
使用 man otool 命令查看 otool 的幫助文檔
  • lipo命令
lipo -info 文件 // 查看架構(gòu)信息
lipo <file> -thin 目標(biāo)架構(gòu) -output 輸出文件 // 導(dǎo)出某種架構(gòu)
lipo <file1> <file2> -output 輸出文件 // 合并多個(gè)架構(gòu)
  • objdump命令
objdump --macho --private-headers <file>

五、文件結(jié)構(gòu)中各部分內(nèi)容細(xì)節(jié)

源碼頭文件地址:
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/mach-o/loader.h

1. Header

源碼中結(jié)構(gòu)

struct mach_header_64 {
    uint32_t    magic;      /* mach magic number identifier 確定是64位還是32位 */
    int32_t     cputype;    /* cpu specifier */
    int32_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 標(biāo)識(shí)二進(jìn)制文件所支持的功能,主要與系統(tǒng)的加載、鏈接有關(guān)*/
    uint32_t    reserved;   /* reserved */
};
// 通過otool命令查看一個(gè)Mach-O的頭部信息
otool -hv xxx
Mach header
      magic  cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64    ARM64        ALL  0x00     EXECUTE   138      13384   NOUNDEFS DYLDLINK TWOLEVEL WEAK_DEFINES BINDS_TO_WEAK PIE

2. Load Commands

這部分的作用是本質(zhì)就是確定如何加載段segment數(shù)據(jù),主結(jié)構(gòu)是:

struct load_command {
    uint32_t cmd;       /* type of load command */
    uint32_t cmdsize;   /* total size of command in bytes */
};

可以通過MachOView來查看有哪些段命令.

項(xiàng)目 內(nèi)容
加載命令
加載命令段.png
LC_SEGMENT_64 segment段加載指令
LC_DYLD_INF0_0NLY 加載動(dòng)態(tài)鏈接庫信息(重定向地址、弱引用綁定、懶加載綁定、開放函數(shù)等的偏移值等信息)
... ...
2.1 其中LC_SEGMENT_64命令的結(jié)構(gòu)
struct segment_command { /* for 32-bit architectures */
    uint32_t    cmd;        /* LC_SEGMENT  加載命令的類型*/
    uint32_t    cmdsize;    /* includes sizeof section structs  加載命令的所占內(nèi)存大小*/
    char        segname[16];    /* segment name */
    uint32_t    vmaddr;     /* memory address of this segment 段Segment的虛擬內(nèi)存地址*/
    uint32_t    vmsize;     /* memory size of this segment 段Segment的虛擬內(nèi)存大小*/
    uint32_t    fileoff;    /* file offset of this segment 段Segment的在文件中的偏移量*/
    uint32_t    filesize;   /* amount to map from the file 段Segment在文件中所占的內(nèi)存大小*/
    vm_prot_t   maxprot;    /* maximum VM protection 表示頁面所需要的最高內(nèi)存保護(hù)*/
    vm_prot_t   initprot;   /* initial VM protection 表示頁面初始的內(nèi)存保護(hù)*/
    uint32_t    nsects;     /* number of sections in segment 段Segment包含節(jié)區(qū)sections的數(shù)量*/
    uint32_t    flags;      /* flags 表示段的標(biāo)志信息*/
}
  • 結(jié)構(gòu)體中segname是加載目標(biāo)段Segment的名稱,常見的段segment有四個(gè)(可以從上面圖中看到)
    __PAGEZERO : 在可執(zhí)行文件有的,動(dòng)態(tài)庫里沒有,這個(gè)段開始地址為0(NULL指針指向的位置),是一個(gè)不可讀、不可寫、不可執(zhí)行的空間,能夠在空指針訪問時(shí)拋出異常。
    __TEXT:代碼段,里面主要是存放代碼的,該段是可讀可執(zhí)行,但是不可寫;
    __DATA:數(shù)據(jù)段,里面主要是存放數(shù)據(jù),該段是可讀可寫,但不可執(zhí)行;
    __LINKEDIT:用于存放簽名信息,該段是只可讀,不可寫不可執(zhí)行;
2.1 每個(gè)命令包含的內(nèi)容

我們?cè)贛achOView展開LC_SEGMENT_64(__TEXT)LC_SEGMENT_64(__DATA),可以看到很多的section header. 這個(gè)是數(shù)據(jù)段和代碼段的各個(gè)section 的頭文件。

struct section_64 { /* for 64-bit architectures */
    char        sectname[16];   /* name of this section */
    char        segname[16];    /* segment this section goes in */
    uint64_t    addr;       /* memory address of this section */
    uint64_t    size;       /* size in bytes of this section */
    uint32_t    offset;     /* file offset of this section */
    uint32_t    align;      /* section alignment (power of 2) */
    uint32_t    reloff;     /* file offset of relocation entries */
    uint32_t    nreloc;     /* number of relocation entries */
    uint32_t    flags;      /* flags (section type and attributes)*/
    uint32_t    reserved1;  /* reserved (for offset or index) */
    uint32_t    reserved2;  /* reserved (for count or sizeof) */
    uint32_t    reserved3;  /* reserved */
};

sectname:section的名稱,常見的section有_text、stubs等等;
segname:當(dāng)前section所隸屬的Segment,例如__TEXT(代碼段);
addr: section在內(nèi)存的起始位置;
size: section所占內(nèi)存大??;
offset: section在文件中的偏移量;
align:字節(jié)大小對(duì)齊,2的align次方;
reloff:重定位入口的文件偏移;
nreloc: 需要重定位的入口數(shù)量;
flags:包含section的type和attributes;

3. Data部分

Data部分主要放的是__Text段和__DATA段的數(shù)據(jù),根據(jù)不同功能分為不同的節(jié)(section)。
段數(shù)據(jù)的頭部信息是存放在Load Commands中的.

段數(shù)據(jù)部分

代碼段和數(shù)據(jù)段的各個(gè)節(jié)分別代表什么可以通過這篇linkmap文章來了解
通過LinkMap來了解Mach-O

六、MachO里面各部分占用大小

我們還可以通過size命令行查看一個(gè)Mach-O大小信息:

// 假設(shè)app工程名xxx,
// -l 參數(shù)可以顯示目標(biāo)文件的完整節(jié)(section)和段(segment)信息
// -m 參數(shù)用于指定目標(biāo)文件的格式
size -l -m xxx.app/xxx 
輸出文件信息包含四個(gè)部分:
Segment __PAGEZERO
Segment __TEXT
Segment __DATA
Segment __LINKEDIT
具體的信息可以打印你APP的對(duì)照看

接下來看看每個(gè)段的打印信息具體是什么意思。

  • Segment __PAGEZERO: 4294967296 (zero fill) (vmaddr 0x0 fileoff 0)
    該段信息指的是一個(gè)名為 __PAGEZERO 的段(segment),其大小為 4GB,對(duì)應(yīng)的是零填充的內(nèi)存。__PAGEZERO 段的虛擬內(nèi)存地址(vmaddr)為 0x0,文件偏移量(fileoff)為 0。
  • Segment __TEXT: 43319296 (vmaddr 0x100000000 fileoff 0)的意思?
    該段信息指的是一個(gè)名為 __TEXT 的段(segment),其大小為 約等于 41.31 MB。__TEXT 段的虛擬內(nèi)存地址(vmaddr)為 0x100000000,文件偏移量(fileoff)為 0。虛擬內(nèi)存地址指示了段在程序運(yùn)行時(shí)被加載到內(nèi)存的位置,而文件偏移量指示了段在目標(biāo)文件中的位置。
  • Segment __DATA: 5849088 (vmaddr 0x102950000 fileoff 43319296)
    該段信息指的是一個(gè)名為 __DATA 的段(segment),其大小約等于 5.572 MB。__DATA 段的虛擬內(nèi)存地址(vmaddr)為 0x102950000,文件偏移量(fileoff)為 43319296。
  • Segment __LINKEDIT: 2736128 (vmaddr 0x102ee4000 fileoff 48840704)
    該段信息指的是一個(gè)名為 __LINKEDIT 的段(segment),其大小約等于 2.61 MB。__LINKEDIT 段的虛擬內(nèi)存地址(vmaddr)為 0x102ee4000,文件偏移量(fileoff)為 48840704。

1. __PAGEZERO

在 Mach-O 文件格式中,__PAGEZERO 段用于標(biāo)識(shí)虛擬內(nèi)存空間的起始位置,并指示該段之前的內(nèi)存區(qū)域應(yīng)該被清零填充。__PAGEZERO(又稱為 Page Zero)是一個(gè)特殊的節(jié)(Section)名稱,它在 Mach-O(Mach Object)文件中定義了虛擬地址空間的第一個(gè)頁。它沒有任何實(shí)際的可執(zhí)行代碼或數(shù)據(jù),僅作為一種占位符存在,用于確保虛擬內(nèi)存空間的連續(xù)性和保護(hù)。

一些主要的特點(diǎn)和作用如下:

  • 安全保護(hù):__PAGEZERO 節(jié)的存在是為了提供一種安全機(jī)制,用于檢測(cè)和防止針對(duì)軟件漏洞的攻擊。通過將可執(zhí)行文件或可加載文件的第一個(gè)頁設(shè)置為沒有權(quán)限的頁,可以防止非法訪問者利用指向第一個(gè)頁的指針進(jìn)行漏洞利用。

  • 空節(jié):__PAGEZERO 節(jié)本身不包含實(shí)際的代碼或數(shù)據(jù)。它的大小通常為0字節(jié),所以在執(zhí)行時(shí)不會(huì)占用任何實(shí)際內(nèi)存。

  • 地址空間布局:__PAGEZERO 節(jié)通常位于可執(zhí)行文件或可加載文件的開始位置,即位于虛擬地址空間的最低部分。它的存在確保了后續(xù)節(jié)的虛擬地址是從一個(gè)明確定義的位置開始的。

2.__TEXT

在 Mach-O 文件格式中,__TEXT 段包含了可執(zhí)行程序的實(shí)際代碼和只讀數(shù)據(jù)。它是二進(jìn)制文件中的一個(gè)重要段,存儲(chǔ)了程序的代碼段和只讀數(shù)據(jù)段。

3.__DATA

在 Mach-O 文件格式中,__DATA 段存儲(chǔ)了可執(zhí)行程序的靜態(tài)變量和全局變量等數(shù)據(jù)。它包含了程序在運(yùn)行時(shí)的可寫數(shù)據(jù)段,即存儲(chǔ)程序在運(yùn)行過程中產(chǎn)生的數(shù)據(jù)的空間。

4. __LINKEDIT

在 Mach-O 文件格式中,__LINKEDIT 段存儲(chǔ)與鏈接器相關(guān)的信息,比如符號(hào)表、重定位信息等。鏈接器主要負(fù)責(zé)將不同的目標(biāo)文件合并成可執(zhí)行文件,__LINKEDIT 段存儲(chǔ)與該過程相關(guān)的一些信息,因此該段也被稱為鏈接器的信息段。

具體來說,__LINKEDIT 段包含以下內(nèi)容:

  • 符號(hào)表(Symbol Table):用于存儲(chǔ)程序中定義和引用的各種符號(hào)(變量、函數(shù)、類等)的信息,鏈接器通過符號(hào)表進(jìn)行符號(hào)解析和重定位等操作。
  • 字符串表(String Table):存儲(chǔ)符號(hào)表中的字符串,用于標(biāo)識(shí)符號(hào)的名稱。
  • 動(dòng)態(tài)符號(hào)表(Dynamic Symbol Table):包含一些在運(yùn)行時(shí)動(dòng)態(tài)加載的符號(hào)信息。
  • 重定位表(Relocation Table):存儲(chǔ)在鏈接過程中需要進(jìn)行地址重定位的部分,包括指令中需要修改的地址和重定位類型等信息。
    __LINKEDIT 段的存在使得鏈接器能夠在程序運(yùn)行時(shí)解析和重定位符號(hào),從而正確地連接和加載各種模塊。

相關(guān)資料:
Mach-O入門理解
Mach-O
iOS逆向06 -- Mach-O

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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