什么是 Mach-O
????Mach-O 其實(shí)是 Mach Object 文件格式的縮寫,它是一種用于可執(zhí)行文件、目標(biāo)代碼、動(dòng)態(tài)庫(kù)的文件格式,作為 a.out 格式的替代, Mach-O 提供了更強(qiáng)的擴(kuò)展性。在 OS X 中,內(nèi)核擴(kuò)展、命令行工具、應(yīng)用程序、框架和庫(kù)(共享和靜態(tài))是使用 Mach-O 文件實(shí)現(xiàn)的。可以在 Mach-O Programming Topics 查看相關(guān)介紹。
分析 Mach-O 文件的工具
-
/usr/bin/lipo
查看二進(jìn)制文件信息,可以生成或者拆分多架構(gòu)文件 -
/usr/bin/file
顯示文件的類型。對(duì)于多架構(gòu)文件,它會(huì)顯示構(gòu)成存檔的每個(gè)圖像的類型。 -
/usr/bin/otool
列出了 Mach-O 文件中特定部分和段的內(nèi)容。它包括每個(gè)支持的體系結(jié)構(gòu)的符號(hào)反匯編器,并且它知道如何格式化許多常見(jiàn)節(jié)類型的內(nèi)容。 -
/usr/bin/pagestuff
顯示關(guān)于組成圖像的每個(gè)邏輯頁(yè)面的信息,包括每個(gè)頁(yè)面中包含的部分和符號(hào)的名稱。此工具不適用于包含多個(gè)架構(gòu)的圖像的二進(jìn)制文件。 -
/usr/bin/nm
允許查看目標(biāo)文件符號(hào)表的內(nèi)容。 -
MachoView.app
MachoView.app
在查看 Mach-O 文件之前,有必要了解MachOView.app 工具,MachOView工具屬于免費(fèi)開(kāi)源項(xiàng)目,源代碼可在Github-MachOView下載,可以在 Mac 中查看 Mach-O文件的詳細(xì)信息
Mach-O 文件結(jié)構(gòu)
一個(gè) Mach-O 文件包含三個(gè)主要區(qū)域(如下所示):
- Header:指定文件的目標(biāo)架構(gòu)
- Load commands :指定文件的邏輯結(jié)構(gòu)和文件在虛擬內(nèi)存中的布局。
- Raw segment data:包含加載命令中定義的段的原始數(shù)據(jù)。
當(dāng)然,可以通過(guò) MachOView 對(duì) App 可執(zhí)行文件進(jìn)行查看,Mach-O 文件里面的內(nèi)容如下圖所示:
Mach64 Header
每個(gè) Mach-O 文件的開(kāi)頭都有一個(gè) Header ,用于將文件標(biāo)識(shí)為 Mach-O 文件。Header 包含該二進(jìn)制文件的一般信息。字節(jié)順序、架構(gòu)類型、加載指令的數(shù)量等。使得可以快速確認(rèn)一些信息,比如當(dāng)前文件用于32位還是64位,對(duì)應(yīng)的處理器是什么、文件類型是什么
Mach64 Header 結(jié)構(gòu)
在 macho/loader.h 中,可以查看到 Mach-O header 信息結(jié)構(gòu)代碼如下:
struct mach_header_64 {
uint32_t magic; // 64位還是32位
cpu_type_t cputype;
cpu_subtype_t cpusubtype; // CPU 子類型,比如 armv8 CPU_SUBTYPE_ARM_64
uint32_t filetype; // 文件類型 MH_EXECUTE
uint32_t ncmds; // load commands 的數(shù)量
uint32_t sizeofcmds; // load commands 大小
uint32_t flags; // 標(biāo)簽
uint32_t reserved; // 保留字段
};
????如上面代碼所示,包含了
-
magic
表示是 64 位還是 32 位的、 -
cputype
CPU 類型,比如CPU_TYPE_ARM
,可以在macho/machine.h
文件中查看其他。 -
cpusubtype
CPU 子類型,比如CPU_SUBTYPE_ARM64_ALL
,可以在macho/machine.h
文件中查看其他。 -
filetype
文件類型 -
ncmds
load commands 的數(shù)量和大小等文件信息 -
sizeofcmds
load commands 大小 -
flags
標(biāo)志位,標(biāo)識(shí)二進(jìn)制文件支持的功能。 -
reserved
保留字段
Mach-O 文件類型
其中,文件類型 filetype
表示了當(dāng)前 Mach-O 屬于哪種類型。下面源代碼列舉了 filetype
的全部類型:
/*
* Constants for the filetype field of the mach_header
*/
#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. */
其中,部分類型是我們開(kāi)發(fā)中常用的:
- OBJECT,指的是 .o 文件或者 .a 文件;
- EXECUTE,指的是 IPA 拆包后的可執(zhí)行文件;
- DYLIB,指的是 .dylib 或 .framework 文件;
- DYLINKER,指的是動(dòng)態(tài)鏈接器;
- DSYM,指的是保存有符號(hào)信息用于分析閃退信息的文件。
Load Commands
緊隨 Header 的是一系列的 Load Commands ,負(fù)責(zé)描述文件在虛擬內(nèi)存中邏輯結(jié)構(gòu)和布局,這些加載指令清晰地告訴加載器如何處理二進(jìn)制數(shù)據(jù),有些命令是由內(nèi)核處理的,有些是由動(dòng)態(tài)鏈接器處理的。除了其他信息,Load Commands 可以指定:
文件在虛擬內(nèi)存中的初始布局
符號(hào)表的位置(用于動(dòng)態(tài)鏈接)
程序主線程的初始執(zhí)行狀態(tài)
包含主可執(zhí)行文件的導(dǎo)入符號(hào)定義的共享庫(kù)的名稱
Load Commands結(jié)構(gòu)
macho/loader.h 中,可以查看到 load_command
信息結(jié)構(gòu)代碼如下:
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
Load Commands 類型
下面列舉了 load commands
全部的類型:
/* Constants for the cmd field of all load commands, the type */
#define LC_SEGMENT 0x1 /* segment of this file to be mapped */
#define LC_SYMTAB 0x2 /* link-edit stab symbol table info */
#define LC_SYMSEG 0x3 /* link-edit gdb symbol table info (obsolete) */
#define LC_THREAD 0x4 /* thread */
#define LC_UNIXTHREAD 0x5 /* unix thread (includes a stack) */
#define LC_LOADFVMLIB 0x6 /* load a specified fixed VM shared library */
#define LC_IDFVMLIB 0x7 /* fixed VM shared library identification */
#define LC_IDENT 0x8 /* object identification info (obsolete) */
#define LC_FVMFILE 0x9 /* fixed VM file inclusion (internal use) */
#define LC_PREPAGE 0xa /* prepage command (internal use) */
#define LC_DYSYMTAB 0xb /* dynamic link-edit symbol table info */
#define LC_LOAD_DYLIB 0xc /* load a dynamically linked shared library */
#define LC_ID_DYLIB 0xd /* dynamically linked shared lib ident */
#define LC_LOAD_DYLINKER 0xe /* load a dynamic linker */
#define LC_ID_DYLINKER 0xf /* dynamic linker identification */
#define LC_PREBOUND_DYLIB 0x10 /* modules prebound for a dynamically */
/* linked shared library */
#define LC_ROUTINES 0x11 /* image routines */
#define LC_SUB_FRAMEWORK 0x12 /* sub framework */
#define LC_SUB_UMBRELLA 0x13 /* sub umbrella */
#define LC_SUB_CLIENT 0x14 /* sub client */
#define LC_SUB_LIBRARY 0x15 /* sub library */
#define LC_TWOLEVEL_HINTS 0x16 /* two-level namespace lookup hints */
#define LC_PREBIND_CKSUM 0x17 /* prebind checksum */
/*
* load a dynamically linked shared library that is allowed to be missing
* (all symbols are weak imported).
*/
#define LC_LOAD_WEAK_DYLIB (0x18 | LC_REQ_DYLD)
#define LC_SEGMENT_64 0x19 /* 64-bit segment of this file to be
mapped */
#define LC_ROUTINES_64 0x1a /* 64-bit image routines */
#define LC_UUID 0x1b /* the uuid */
#define LC_RPATH (0x1c | LC_REQ_DYLD) /* runpath additions */
#define LC_CODE_SIGNATURE 0x1d /* local of code signature */
#define LC_SEGMENT_SPLIT_INFO 0x1e /* local of info to split segments */
#define LC_REEXPORT_DYLIB (0x1f | LC_REQ_DYLD) /* load and re-export dylib */
#define LC_LAZY_LOAD_DYLIB 0x20 /* delay load of dylib until first use */
#define LC_ENCRYPTION_INFO 0x21 /* encrypted segment information */
#define LC_DYLD_INFO 0x22 /* compressed dyld information */
#define LC_DYLD_INFO_ONLY (0x22|LC_REQ_DYLD) /* compressed dyld information only */
#define LC_LOAD_UPWARD_DYLIB (0x23 | LC_REQ_DYLD) /* load upward dylib */
#define LC_VERSION_MIN_MACOSX 0x24 /* build for MacOSX min OS version */
#define LC_VERSION_MIN_IPHONEOS 0x25 /* build for iPhoneOS min OS version */
#define LC_FUNCTION_STARTS 0x26 /* compressed table of function start addresses */
#define LC_DYLD_ENVIRONMENT 0x27 /* string for dyld to treat
like environment variable */
#define LC_MAIN (0x28|LC_REQ_DYLD) /* replacement for LC_UNIXTHREAD */
#define LC_DATA_IN_CODE 0x29 /* table of non-instructions in __text */
#define LC_SOURCE_VERSION 0x2A /* source version used to build binary */
#define LC_DYLIB_CODE_SIGN_DRS 0x2B /* Code signing DRs copied from linked dylibs */
#define LC_ENCRYPTION_INFO_64 0x2C /* 64-bit encrypted segment information */
#define LC_LINKER_OPTION 0x2D /* linker options in MH_OBJECT files */
#define LC_LINKER_OPTIMIZATION_HINT 0x2E /* optimization hints in MH_OBJECT files */
#define LC_VERSION_MIN_TVOS 0x2F /* build for AppleTV min OS version */
#define LC_VERSION_MIN_WATCHOS 0x30 /* build for Watch min OS version */
#define LC_NOTE 0x31 /* arbitrary data included within a Mach-O file */
#define LC_BUILD_VERSION 0x32 /* build for platform min OS version */
#define LC_DYLD_EXPORTS_TRIE (0x33 | LC_REQ_DYLD) /* used with linkedit_data_command, payload is trie */
#define LC_DYLD_CHAINED_FIXUPS (0x34 | LC_REQ_DYLD) /* used with linkedit_data_command */
#define LC_FILESET_ENTRY (0x35 | LC_REQ_DYLD) /* used with fileset_entry_command */
通過(guò) MachOView 來(lái)繼續(xù)查看 LoadCommands 內(nèi)容
對(duì)應(yīng)的描述如下表格:
load commands Type | 描述 |
---|---|
LG_SEGMENT_64 | 將文件中(32位或64位)的段映射到進(jìn)程地址空間中 |
LC_DYLD_INFO_ONLY | 動(dòng)態(tài)鏈接相關(guān)信息 rebase、bind |
LC_SYMTAB | 符號(hào)表 |
LC_DYSYMTAB | 動(dòng)態(tài)符號(hào)表 |
LC_UUID | 文件的 UUID |
LC_VERSION_MIN_IPHONES | 支持最低的操作系統(tǒng)版本 |
LC_SOURCE_VERSION | 源代碼版本 |
LC_MAIN | 程序入口地址和棧大小 |
LC_LOAD_DYLIB | 依賴系統(tǒng)庫(kù)路徑 |
LC_RPATH | 運(yùn)行時(shí)優(yōu)先搜索依賴庫(kù)的目錄路徑 |
LC_FUNCTION_STARTS | 函數(shù)起始地址表 |
LC_CODE_SIGNATURE | 代碼簽名 |
Segment_Command
LC_SEGMENT_64
和 LC_SEGMENT
是加載的主要命令,它負(fù)責(zé)指導(dǎo)內(nèi)核來(lái)設(shè)置進(jìn)程的內(nèi)存空間,下面是
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 */
};
-
cmdsize
代表load command
的大小 -
segname
段名稱-
__PAGEZERO
作為可執(zhí)行文件的第一個(gè)段。該段位于虛擬內(nèi)存位置 0 并且沒(méi)有分配保護(hù)權(quán)限。該__PAGEZERO段是當(dāng)前體系結(jié)構(gòu)的一個(gè)完整 VM 頁(yè)面的大小。因?yàn)?__PAGEZERO
段中沒(méi)有數(shù)據(jù),所以不占用文件中的空間 -
__TEXT
對(duì)應(yīng)的就是代碼段 -
__DATA
對(duì)應(yīng)的是可讀/可寫的數(shù)據(jù) -
__OBJC
包含由 Objective-C 語(yǔ)言運(yùn)行時(shí)支持庫(kù)使用的數(shù)據(jù) -
__LINKEDIT
是支持dyld的,里面包含一些符號(hào)表等數(shù)據(jù) -
__IMPORT
包含符號(hào)存根和指向未在可執(zhí)行文件中定義的符號(hào)的非惰性指針。此段僅為針對(duì) IA-32 架構(gòu)的可執(zhí)行文件生成。
-
-
VM Address
段的虛擬內(nèi)存地址 -
VM Size
段的虛擬內(nèi)存大小 -
file offset
段在文件中偏移量 -
file size
:段在文件中的大小
將該段對(duì)應(yīng)的文件內(nèi)容加載到內(nèi)存中:從offset處加載 file size大小到虛擬內(nèi)存 vmaddr處 -
nsects
標(biāo)示了Segment中有多少secetion
通過(guò) MachOView 查看 TEXT 段 內(nèi)容
Raw segment data
在 Load Commands 之后,所有的 Mach-O 文件都包含一個(gè)或多個(gè) Segment 的數(shù)據(jù)。Segment 的每個(gè)部分都包含某種特定類型的代碼或數(shù)據(jù),每個(gè) Segment 定義了一個(gè)虛擬內(nèi)存區(qū)域,動(dòng)態(tài)鏈接器將其映射到進(jìn)程的地址空間。Segments 和 Sections 的確切數(shù)量和布局由加載命令和文件類型指定。
Section_64 結(jié)構(gòu)
macho/loader.h 中,可以查看到 section_64
信息結(jié)構(gòu)代碼如下:
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
比如_text、stubs -
segname
該 section 所屬的 segment ,比如__TEXT(主程序),__DATA(數(shù)據(jù)段) -
addr
該 section在內(nèi)存的起始位置 -
size
該 section 的大小 -
offset
該 section 的文件偏移 -
align
字節(jié)大小對(duì)齊 -
reloff
重定位入口的文件偏移 -
nreloc
需要重定位的入口數(shù)量 -
flags
包含 section 的 type 和 attributes
Section 的類型
名稱 | 描述 | 所屬部分 |
---|---|---|
__TEXT,__text | 可執(zhí)行機(jī)器碼。編譯器通常只在此部分中放置可執(zhí)行代碼,而不放置任何類型的表或數(shù)據(jù)。 | __TEXT |
__TEXT,__cstring | 常量 C 字符串。靜態(tài)鏈接器在構(gòu)建最終產(chǎn)品時(shí)合并常量 C 字符串值,刪除重復(fù)項(xiàng)。 | __TEXT |
__TEXT,__picsymbol_stub | 與位置無(wú)關(guān)的間接符號(hào)存根 | __TEXT |
__TEXT,__symbol_stub | 間接符號(hào)存根。 | __TEXT |
__TEXT,__const | 初始化常量。 | __TEXT |
__TEXT,__literal4 | 4 字節(jié)字面量 | __TEXT |
__TEXT,__literal8 | 8 字節(jié)字面量 | __TEXT |
__DATA,__data | 已初始化的可變變量 | __DATA |
__DATA,__la_symbol_ptr | 懶加載符號(hào)指針 | __DATA |
__DATA,__nl_symbol_ptr | 非懶加載符號(hào)指針 | __DATA |
__DATA,__dyld | 動(dòng)態(tài)鏈接器使用的占位符部分 | __DATA |
__DATA,__const | 初始化的可重定位常量 | __DATA |
__DATA,__mod_init_func | 靜態(tài)構(gòu)造函數(shù) | __DATA |
__DATA,__mod_term_func | 模塊終止功能 | __DATA |
__DATA,__bss | 未初始化靜態(tài)變量的數(shù)據(jù) | __DATA |
__DATA,__common | 位于全局范圍內(nèi)的變量聲明 | __DATA |