一、Mach-O 相關(guān)概念簡介
1.概念描述
Mach-O 為 Mach Object 文件格式的縮寫之景,它是一種用于可執(zhí)行文件铲汪、目標(biāo)代碼熊尉、動態(tài)庫罐柳、內(nèi)核轉(zhuǎn)儲的文件格式。作為 a.out 格式的替代狰住,Mach-O 提供了更強(qiáng)的擴(kuò)展性张吉,并提升了 符號表 中信息的訪問速度。
通用二進(jìn)制
通用二進(jìn)制代碼有兩種基本類型:
- 一種類型就是簡單提供兩種獨(dú)立的二進(jìn)制代碼催植,一個(gè)用來對應(yīng)x86架構(gòu)肮蛹,一個(gè)用來對應(yīng)PowerPC架構(gòu)。
- 另外一種類型就是只編寫一個(gè)架構(gòu)的代碼创南,當(dāng)另外一種處理環(huán)境時(shí)讓系統(tǒng)自動調(diào)用模擬器運(yùn)行伦忠,這會導(dǎo)致運(yùn)行速度下降。
多重架構(gòu)二進(jìn)制(胖二進(jìn)制)
在 NeXTSTEP 稿辙,OPENSTEP 和 Mac OS X 中昆码,可以將多個(gè)Mach-O文件組合進(jìn)一個(gè)多重架構(gòu)二進(jìn)制(胖二進(jìn)制)文件中,以用一個(gè)單獨(dú)的二進(jìn)制文件支持多種架構(gòu)的指令集邓深。例如未桥,一個(gè)Mac OS X中的多重架構(gòu)二進(jìn)制可以包含32位和64位的PowerPC代碼,或PowerPC和x86的32位代碼芥备,甚至包含32位的PowerPC代碼冬耿,64位PowerPC代碼,32位x86代碼和64位x86代碼萌壳。
2.對 Mach-O 文件進(jìn)行操作
-
使用
file Mach-O
命令查看 Mach-O 文件類型
查看文件架構(gòu).png -
使用
lifo -info <Mach-O>
命令查看文件架構(gòu)
文件架構(gòu).png 使用
lipo
命令拆分某種架構(gòu)
lipo <Mach-O> -thin <架構(gòu)名> -output <輸出文件路徑>
- 使用
lipo
命令合并多種架構(gòu)
lipo -create <Mach-O1> <Mach-O2> -output <輸出文件路徑>
二袱瓮、查看可執(zhí)行文件
1.使用 otool
命令查看 Mach-O 文件
- 查看可執(zhí)行文件的動態(tài)鏈接庫
otool -L WeChat
- 查看頭信息
otool -h WeChat
- 查看是否加密
otool -l WeChat | grep crypt
cryptid 為 0 時(shí)表示無加密缤骨,即已砸殼;
cryptid 為 1 時(shí)表示有加密尺借,即未砸殼绊起。
- 查看頭信息
otool -h DingTalk
2.使用 MachOView 軟件查看
用 MachOView 打開可執(zhí)行文件可以看到有胖二進(jìn)制文件的結(jié)構(gòu)如下圖:
可以看到可執(zhí)行文件包括三個(gè)部分:
- Fat Header:包含架構(gòu)數(shù)量及不同架構(gòu)指令集的簡單信息
- Executable(ARM_V7):arm_v7 架構(gòu)對應(yīng)的指令集
- Executable(ARM64_ALL):arm64 架構(gòu)對應(yīng)的指令集
三、Mach-O 文件結(jié)構(gòu)
Mach-O主要分為三個(gè)部分:Header燎斩、Load commands和Data虱歪。
- Header:包含字節(jié)順序、架構(gòu)類型栅表、加載指令的數(shù)量等笋鄙,使得系統(tǒng)可以快速確認(rèn)一些信息,比如當(dāng)前文件用于32位還是64位怪瓶,對應(yīng)的處理器是什么萧落、文件類型是什么。
- Load commands:它是一張包含很多內(nèi)容的表,內(nèi)容包括區(qū)域的位置找岖、符號表陨倡、動態(tài)符號表等。每個(gè)加載指令都包含一個(gè)元信息宣增,比如指令類型玫膀、名稱、在二進(jìn)制文件中的位置等等爹脾。
- Data:通常是對象文件中最大的部分帖旨。主要包含代碼、數(shù)據(jù)灵妨,例如符號表解阅,動態(tài)符號表等等。Data 中包含若干個(gè) segment (段)泌霍,每個(gè) segment 下又有若干個(gè) section(節(jié))货抄。
1. Header
頭文件就是該可執(zhí)行文件的信息概要
該部分結(jié)構(gòu)可以 打開 <macho-o/loader.h> 查看
/*
* 64位結(jié)構(gòu)頭
*/
struct mach_header_64 {
uint32_t magic; // Mach-O 文件的
cpu_type_t cputype; // CPU 架構(gòu)
cpu_subtype_t cpusubtype; // CPU 架構(gòu)子版本
uint32_t filetype; // 文件類型。常見的有 MH_OBJECT(目標(biāo)文件)朱转、MH_EXECUTE(可執(zhí)行文件)蟹地、MH_DYLIB(動態(tài)庫)、MH_DYLINKER(動態(tài)鏈接器)
uint32_t ncmds; // 加載指令數(shù)量
uint32_t sizeofcmds; // 加載指令大小
uint32_t flags; // dyld 加載需要的一些標(biāo)記
uint32_t reserved; // 64 位的保留字段
};
2. Load commands
Load commands 作用是讓系統(tǒng)知道如何加載文件中的信息藤为,對系統(tǒng)內(nèi)核加載器和動態(tài)鏈接器起引導(dǎo)作用怪与。
從上圖可以看到,Load command 包含以下部分:
- LC_SEGMENT_64:定義一個(gè)段缅疟,加載后被映射到內(nèi)存中分别,包括里面的節(jié)。相當(dāng)與一個(gè)數(shù)據(jù)索引存淫,指明了不同類型數(shù)據(jù)的地址和大小
- LC_DYLD_INFO_ONLY:記錄了有關(guān)鏈接的重要信息耘斩,包括 __LINKEDIT 中動態(tài)鏈接相關(guān)信息的具體偏移和大小
- LC_SYMTAB:為文件定義符號表和字符串表,在鏈接文件時(shí)被連接器使用桅咆,同時(shí)也用于調(diào)試器映射符號到源文件括授。
- LC_DYSYMTAB:將符號表中給出符號的額外符號信息提供給動態(tài)鏈接器
- LC_LOAD_DYLINKER:默認(rèn)的加載器路徑
- LC_UUID:用于標(biāo)識 Mach-O 文件的 ID,也用于奔潰堆棧和符號文件的對應(yīng)解析
- LC_VERSION_MIN_IPHONEOS:系統(tǒng)要求的最低版本
- LC_SOURCE_VERSION:構(gòu)建二進(jìn)制文件的源代碼版本號
- LC_MAIN:程序的入口。dyld 獲取改地址岩饼,然后跳轉(zhuǎn)到該處執(zhí)行
- LC_ENCRYPTION_INFO_64:文件是否加密標(biāo)志刽脖,加密內(nèi)容的偏移和大小
- LC_LOAD_DYLIB:依賴的動態(tài)庫
- LC_RPATH:Runpath Search Paths, @rpatch 搜索的路徑
- LC_FUNCTION_STARTS:函數(shù)起始地址表,使用調(diào)試器和其他程序能很容易看到一個(gè)地址是否在函數(shù)內(nèi)
- LC_DATA_IN_CODE:定義在代碼段內(nèi)的非指令的表
- LC_CODE_SIGNATURE:代碼簽名信息
3. Data
LC_SEGMENT_64 加載指令映射的就是 Data 中的數(shù)據(jù)偏移和大小忌愚,該文件組要包含四個(gè)段:
- __PAGEZERO:空指針陷阱段,映射到虛擬內(nèi)容控件的第一頁却邓,用于捕捉對 NULL 指針的引用
- __TEXT:代碼段/只讀數(shù)據(jù)段
- __DATA:讀取和寫入數(shù)據(jù)的段
- __LINKEDIT:動態(tài)鏈接器需要使用的信息硕糊,包括重定位信息、綁定信息、懶加載信息等
下面是 64 位 segment 段的數(shù)據(jù)結(jié)構(gòu)
struct segment_command_64 { /* for 64-bit architectures */
uint32_t cmd; // 指令類型
uint32_t cmdsize; // 指令大小
char segname[16];// 段的名字
uint64_t vmaddr; // 映射到虛擬地址的偏移
uint64_t vmsize; // 映射到虛擬地址的大小
uint64_t fileoff; // 對應(yīng)當(dāng)前架構(gòu)文件的偏移
uint64_t filesize; // 文件的大小
vm_prot_t maxprot; // 段頁面的最高內(nèi)存保護(hù)
vm_prot_t initprot; // 初始內(nèi)存保護(hù)
uint32_t nsects; // 包含的節(jié)的個(gè)數(shù)
uint32_t flags; // 段頁面的標(biāo)志
};
段中包含的節(jié)的數(shù)據(jù)結(jié)構(gòu)
struct section_64 { /* for 64-bit architectures */
char sectname[16]; // 節(jié)的名字
char segname[16]; // 所屬段的名字
uint64_t addr; // 映射到虛擬地址的偏移
uint64_t size; // 節(jié)的大小
uint32_t offset; // 節(jié)在當(dāng)前架構(gòu)文件中的偏移
uint32_t align; // 節(jié)的字節(jié)對齊大小
uint32_t reloff; // 重定位入口的文件偏移
uint32_t nreloc; // 重定位入口的個(gè)性
uint32_t flags; // 節(jié)的類型和屬性
uint32_t reserved1; // 保留位
uint32_t reserved2; // 保留位
uint32_t reserved3; // 保留位
};
__Text 段中包含的節(jié)
- __text:程序可執(zhí)行的代碼區(qū)域
- __stubs:間接符號存根简十,跳轉(zhuǎn)到懶加載指針表
- __stub_helper:幫助解決懶加載符號加載的輔助函數(shù)
- __objc_methname:方法名
- __objc_classname:類名
- __objc_methtype:方法簽名
- cstring:只讀的 C 風(fēng)格字符串檬某,包含 OC 的部分字符串和屬性名
__Data 段中包含的節(jié)
- __nl_symbol_ptr:非懶加載指針表,在 dylib 加載時(shí)立即綁定值
- __la_symbol_ptr:懶加載指針表螟蝙,第一次調(diào)用是才會綁定值
- __got:非懶加載全局指針表
- __mod_init_func:constructor 函數(shù)
- __mod_term_func:destructor 函數(shù)
- __cfstring:OC 字符串
- __objc_classlist:程序中類的列表
- __objc_nlclslist:程序中自己實(shí)現(xiàn)了+load 方法的類
- __objc_protolist:協(xié)議列表
- __objc_classrefs:被引用的類列表