希望通過本文來記錄對(duì)于iOS開發(fā)對(duì)Mach-O需要有的基本了解。
蘋果推出Mach-O的背景:
- 過渡至基于 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ǔ)和交互。
- 提高性能和可擴(kuò)展性:Mach-O 文件格式相對(duì)于舊的目標(biāo)文件格式(如 a.out 格式)具有更好的性能和可擴(kuò)展性。它采用了更加緊湊和高效的數(shù)據(jù)結(jié)構(gòu),使得應(yīng)用程序的加載、鏈接和執(zhí)行更高效。這在日益復(fù)雜的應(yīng)用程序和需求下變得尤為重要。
- 支持 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ā)。
- 跨平臺(tái)支持和移植性:Mach-O 文件格式不僅用于 macOS 和 iOS,還可以支持其他基于 Darwin 內(nèi)核的操作系統(tǒng),如 tvOS 和 watchOS。這種一致的文件格式使得開發(fā)者可以更方便地在不同的蘋果平臺(tái)上共享和移植代碼,提高開發(fā)效率和代碼復(fù)用性。
- 操作系統(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í)行文件)。

而在組件化工程里,有一些本地或私有庫我們可能會(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-O 是 Mach 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的方式
- 使用
MachOView:https://github.com/gdbinit/MachOView - otool命令
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ù)段的各個(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



