Mach-O文件格式解析

Mach-o文件

Mach-O 是iOS/macOS系統(tǒng)上應(yīng)用程序的格式

通用二進制文件(胖二進制文件)

因為MacOSX最早運行于PowerPC(PPC)架構(gòu),后來移植到Intel,通用二進制的文件可以在PPC和X86兩種處理器上執(zhí)行,所以蘋果提出了通用二進制這個概念,iOS應(yīng)用需要支持不同的CPU架構(gòu)(armv7,arm64),所以默認打包出來的是一個Universal Binary(通用二進制)格式文件,使用file命令或者lipo命令查看文件信息


image.png

image.png

查看抖音APP的可執(zhí)行文件


image.png

積目現(xiàn)在只支持arm64 CPU架構(gòu),所以打出的包只有arm64架構(gòu)格式。

胖二進制文件只是將不同架構(gòu)的Mach-O文件打包在一起,再在文件起始位置加上fat_header結(jié)構(gòu)來說明包含的Mach-O文件支持架構(gòu)和偏移地址信息

image.png

Fat Header 可以在XNU源碼mach-o/fat.h中看到定義

image.png

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

fat_arch數(shù)據(jù)之后就是每一個Mach-O文件的分布了,Mach-O文件是由3部分組成:Mach-O頭部(Header), 加載命令(Load Commands), 數(shù)據(jù)塊(Data)

image.png

Mach-O頭部(Header)

Mach-O頭部(Header)保存了CPU架構(gòu),大小端序,文件類型,加載命令數(shù)量等一些基本信息,通過頭部就能按順序向下解析Match-O文件

Mach-O頭部定義

頭部信息定義在XNU源碼中mach-o/loader.h中,32位架構(gòu)的CPU使用mach_header結(jié)構(gòu)體來描述頭部信息,64位架構(gòu)的CPU使用mach_header_64結(jié)構(gòu)體描述頭部信息

image.png

filetype文件類型也定義在XNU源碼中mach-o/loader.h中,列舉了一些常見的文件類型

image.png

Mach-O文件的標志信息也定義在XNU源碼中mach-o/loader.h中
標志信息中的 MH_PIE 只在MH_EXECUTE中使用,表示啟用ASLR地址空間布局隨機化來增加程序的安全性

image.png

除了使用otool工具在命令行查看Mach-O文件之外還可以使用MachOView可視化工具直接打開Mach-O文件,

使用MachOView打開dyld


image.png

使用MachOView打開UIKit


image.png

Mach-O加載命令

通過加載命令(Load Commands)告訴加載器如何處理二進制數(shù)據(jù),有些命令是內(nèi)核處理的,有些是動態(tài)鏈接器處理的。

加載命令的結(jié)構(gòu)體定義

image.png

XNU源碼—加過注釋mach-o/loader.h中可以看到LoadCommands命令的定義
使用MachOView打開的Mach-O文件中LoadCommands部分結(jié)構(gòu)圖
image.png

重要的一些命令
LC_SEGMENT 和 LC_SEGMENT_64

這兩個都是段的加載命令,每個段定義了一個虛擬內(nèi)存區(qū)域,動態(tài)鏈接器負責把這個區(qū)域映射到進程地址空間 LC_SEGMENT使用 segment_command 結(jié)構(gòu)體標識, LC_SEGMENT_64使用 segment_command_64結(jié)構(gòu)體表示

segment_command_64在XNU源碼中的定義

image.png

按照LC_SEGMENT_64加載Segment到內(nèi)存中:系統(tǒng)從文件偏移量為fileoff處加載filesize字節(jié)內(nèi)容到虛擬內(nèi)存地址vmaddr處的vmsize字節(jié)。每個段的頁面都根據(jù)initprot進行初始化,initprot指定了如何通過讀,寫,執(zhí)行位初始化頁面的保護級別,段的保護設(shè)置可以動態(tài)改變,但是不能超過maxprot中指定的值(在iOS中 +X和+W是互斥的)

在MachOView中看segment_command_64的內(nèi)容


image.png

_PAGEZERO
靜態(tài)鏈接器創(chuàng)建了_PAGEZERO,作為可執(zhí)行文件的第一個段,這個段在虛擬內(nèi)存中的位置和大小都是0,不能讀寫,不能執(zhí)行

_TEXT
包含了可執(zhí)行的一些代碼合其他一些只讀數(shù)據(jù),靜態(tài)鏈接器設(shè)置該段的虛擬內(nèi)存的權(quán)限為可讀,可執(zhí)行,這些代碼可以被進程執(zhí)行,但是不能修改。
在MachOView中查看_TEXT段的section定義:


image.png

_DATA
包含了將會被更改的數(shù)據(jù),靜態(tài)鏈接器設(shè)置該段的虛擬內(nèi)存權(quán)限為可讀,可寫
在MachOView中查看_DATA段的section定義:


image.png

非懶綁定:在動態(tài)鏈接器加載程序的時候就會綁定真實的調(diào)用地址,之后直接使用即可??梢岳斫鉃橹鲃咏壎?br> 懶綁定:只有在方法被調(diào)用的時候才會去尋找對應(yīng)的調(diào)用地址,然后再執(zhí)行,可以理解為被動綁定

一個段可以包含0個或者多個Section,32位和64位分別用section和section_64結(jié)構(gòu)體表示


image.png

段和section的命名規(guī)則:大寫代表的是段,小寫代表的是section

LC_LOAD_DYLIB 和 LC_LOAD_WEAK_DYLIB

指向的都是程序依賴的加載庫的信息使用MachOView查看LC_LOAD_DYLIB加載命令可以看到當前Mach-O文件依賴的程序庫


image.png

LC_LOAD_DYLIB在XNU源碼中是使用dylib_command 結(jié)構(gòu)體定義的


image.png

image.png

dylib_command對應(yīng)的結(jié)構(gòu)也可能是LC_LOAD_WEAK_DYLIB, LC_LOAD_DYLIB 和 LC_LOAD_WEAK_DYLIB它們都表示需要加載一個動態(tài)庫,通過LC_LOAD_WEAK_DYLIB聲明的依賴庫是可選的,如果加載的過程中缺少這些動態(tài)庫主程序會繼續(xù)執(zhí)行,不會有什么影響,通過LC_LOAD_DYLIB加載動態(tài)庫,依賴庫若是沒有找到,加載器會放棄并結(jié)束該進程

加載路徑可以是/System/Library/ 和 、usr/lib/ 這種系統(tǒng)路徑,還可以通過@rpath,@executable_path 來指定路徑

@rpath是LC_RPATH 加載命令指定路徑的,在iOS上存放應(yīng)用自己的Framework文件默認路徑為@executable_path/Framework, @executable_path是當前可執(zhí)行文件的目錄,在MacOS上可以通過 install_name_tool工具修改依賴庫的路徑

LC_CODE_SIGNATURE

LC_CODE_SIGNATURE是代碼簽名加載命令,通常位于最后一個段中,描述了Mach-O文件的代碼簽名信息,在iOS中如果簽名校驗不通過,進程會立刻被內(nèi)核用SIGKILL命令殺死,在XNU源碼中使用linkedit_data_command結(jié)構(gòu)體表示

其他命令
  • LC_DYLD_INFO_ONLY
    記錄了動態(tài)鏈接的重要信息,動態(tài)鏈接器要根據(jù)它來進行地址重定向
  • LC_SYMTAB
    文件所使用的符號表
  • LC_DYSYMTAB
    動態(tài)鏈接器所使用的符號表,可以獲取到間接符號表的偏移量
  • LC_LOAD_DYLINKER
    默認的動態(tài)鏈接器路徑 /usr/bin/dyld
  • LC_UUID
    Mach-O文件的唯一標識。DSYM文件和崩潰堆棧中都存在這個值,用來分析對應(yīng)的崩潰位置
  • LC_VERSION_MIN_IPHONEOS
    mach-O文件要求的最低系統(tǒng)版本和Xcode中配置的target有關(guān)系
  • LC_SOURCE_VERSION
    構(gòu)建二進制文件的源代碼版本
  • LC_MAIN
    程序的入口(main函數(shù)地址)。動態(tài)鏈接器獲取到這個地址,然后開始執(zhí)行代碼
  • LC_ENCRYPTION_INFO_64
    文件加密信息,包括加密標記,加密數(shù)據(jù)的偏移大小
  • LC_RPATH
    @path的路徑,指定動態(tài)鏈接器搜索路徑列表
  • LC_FUNCTION_STARTS
    函數(shù)的起始地址表,調(diào)試器可以判斷一個地址是否在這個表的范圍內(nèi)
  • LC_DATA_IN_CODE
    定義在代碼段內(nèi)的非指令表
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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