Mach-O是Mac/iOS上的可執行文件格式,相似於UNIX中的ELF(Extensible Firmware InInterface)。Mach-O文件的的Magic Number爲0xfeedface(32位)和0xfeedfacf(64位)。html
macOS上的可執行文件有三類:c++
可執行文件 | Magic Number | 用途 |
---|---|---|
shell腳本 | \x7FELF | shell腳本,perl,awk等。 |
通用二進制格式 | 0xcafebabe,0xbebafeca | 包含多種架構支持,也叫Fat Binary。 |
Mach-O | 0xfeedface(32位), 0xfeedfacf(64位) | iOS和macOS上使用。 |
lipo是管理Fat Binary的工具,可查看平臺列表,提取特定平臺的Binary等。通常用於macOS平臺上多架構通用二進制文件的處理。git
關於lipo命令的示例請看下文。github
iOS, macOS平臺上有如下幾種類型:shell
注意:可執行文件的格式與mach-o文件不能等同。bootstrap
在iOS 12.2/usr/include/mach-o/header.h中有相關定義。swift
使用MachOView工具來查看Mach-O文件,如查看該main文件:瀏覽器
在該工具中,能夠清晰地看到Mach-O的內部格式,包括Magic Number、CPU Type等,以及各個段:Load Command、__TEXT, __DATA等。緩存
Load Commands展現了Mach-O文件的具體結構,不一樣的結構會採用不一樣的加載命令。sass
這些命令都是load_command結構,cmd字段標記了命令類型,而cmdsize則是命令的長度,即命令二進制的長度。
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
複製代碼
section是相同或類似信息的集合,如.text、.data、.bss section都是不一樣的section。而segment是由多個屬性相同的section組成的。咱們一般說的代碼段和數據段指的其實就是segment。
#define SEG_PAGEZERO "__PAGEZERO" /* the pagezero segment which has no */
/* protections and catches NULL */
/* references for MH_EXECUTE files */
複製代碼
__PAGEZERO是空指針陷阱段,映射到虛擬內存空間的第一頁,用於捕捉對NULL指針的引用。
__PAGEZERO的大小爲 4GB。這 4GB 並非文件的真實大小,可是規定了進程地址空間的前 4GB 被映射爲 不可執行、不可寫和不可讀。這就是爲何當讀寫一個 NULL 指針或更小的值時會獲得一個 EXC_BAD_ACCESS 錯誤。這是操做系統在嘗試防止引發系統崩潰。
代碼區,這一部分是App中的不可變部分,即Read only的。
#define SEG_TEXT "__TEXT" /* the tradition UNIX text segment */
#define SECT_TEXT "__text" /* the real text part of the text */
/* section no headers, and no padding */
#define SECT_FVMLIB_INIT0 "__fvmlib_init0" /* the fvmlib initialization */
/* section */
#define SECT_FVMLIB_INIT1 "__fvmlib_init1" /* the section following the */
/* fvmlib initialization */
/* section */
複製代碼
__TEXT包含了不少其餘的Section:
Section | 解釋 |
---|---|
__TEXT, __text | 主程序代碼段。The compiled machine code for the executable |
__TEXT, __stubs | Stub能夠理解爲一段佔位空間,placeholder,用於符號的lazy binding。 |
__TEXT, __stubs_helper | Stub helper |
__TEXT, __cstring | C語言字符串。Literal string constants (quoted strings in source code) |
__TEXT, __entitlements | __entitlements |
__TEXT, __unwind_info | C語言字符串 |
__TEXT, __const | 常量段(const修飾)。The general constant data for the executable |
__TEXT, __objc_classname | OC的類名 |
__TEXT, __objc_methname | OC方法名稱 |
__TEXT, __objc_methtype | OC方法類型,即方法簽名 |
__stubs和__stubs_helper是給dyld使用的,使得符號能夠被懶綁定(lazy binding),即在首次使用而非加載時就去綁定其符號地址。
注意,objc相關的有__objc_classname,__objc_methname,__objc_methtype,這三部分均爲不可變的。
#define SEG_DATA "__DATA" /* the tradition UNIX data segment */
#define SECT_DATA "__data" /* the real initialized data section */
/* no padding, no bss overlap */
#define SECT_BSS "__bss" /* the real uninitialized data section*/
/* no padding */
#define SECT_COMMON "__common" /* the section common symbols are */
/* allocated in by the link editor */
複製代碼
Section | 解釋 |
---|---|
__DATA_CONST, __got | __got |
__DATA, __got | __got |
__DATA, __data | 已初始化的全局變量。Initialized global variables (for example int a = 1; or static int a = 1;). |
__DATA, __bss | 未初始化的靜態變量。Uninitialized static variables (for example, static int a;). |
__DATA, __const | Constant data needing relocation (for example, char * const p = "foo";). |
__DATA, __cfstring | Core Foundation字符串(CFStringRefs),沒有ARC |
__DATA, __common | 未初始化的外部全局變量。Uninitialized external globals (for example, int a; outside function blocks). |
__DATA, __la_symbol_ptr | 懶綁定的符號指針表。「Lazy」 symbol pointers. Symbol pointers for each undefined function called by the executable. |
__DATA, __nl_symbol_ptr | 非懶綁定的符號指針表。「Non lazy」 symbol pointers. Symbol pointers for each undefined data symbol referenced by the executable. |
__DATA, __objc_classlist | OC的類列表,存儲一個個指向objc_class結構體的指針 |
__DATA, __objc_nlclslist | OC的類列表,+load相關? |
__DATA, __objc_catlist | OC的category列表,存儲一個個指向__objc_category結構體的指針 |
__DATA, __objc_protolist | OC的協議列表,存儲一個個指向protocol_t結構體的指針 |
__DATA, __objc_imginfo | OC的image信息 |
__DATA, __objc_selrefs | 哪些SEL對應的字符串被引用了 |
__DATA, __objc_classrefs | 類的引用,即msg_objSend相關 |
__DATA, __objc_superrefs | super引用,記錄了super方法調用的類。如ViewController中的viewDidLoad中調用了[super viewDidLoad],則ViewController class即被記錄。也能夠理解爲objc_msgSendSuper相關。 |
__DATA, __objc_protorefs | 協議引用 |
__DATA, __objc_ivar | 成員變量 |
__DATA, __objc_const | 這裏的const跟__TEXT的const徹底不一樣。__objc_const指的是OC內存佈局中的不可變部分,即class_ro_t類型。 |
__DATA, __objc_data | 保存類所需的數據? |
__la_symbol_ptr是懶綁定(lazy binding)的符號指針,在加載的時候,並未直接肯定符號地址,而是在第一次調用該函數的時候,經過PLT(Procedure Linkage Table)進行一次懶綁定。而__nl_symbol_ptr則不會進行懶綁定。
OC及runtime相關的
#define SEG_OBJC "__OBJC" /* objective-C runtime segment */
#define SECT_OBJC_SYMBOLS "__symbol_table" /* symbol table */
#define SECT_OBJC_MODULES "__module_info" /* module information */
#define SECT_OBJC_STRINGS "__selector_strs" /* string table */
#define SECT_OBJC_REFS "__selector_refs" /* string table */
複製代碼
icon,tiff資源等。
#define SEG_ICON "__ICON" /* the icon segment */
#define SECT_ICON_HEADER "__header" /* the icon headers */
#define SECT_ICON_TIFF "__tiff" /* the icons in tiff format */
複製代碼
包含須要被動態連接器dyld使用的符號和其餘表,包括符號表、字符串表等。
#define SEG_LINKEDIT "__LINKEDIT" /* the segment containing all structs */
/* created and maintained by the link */
/* editor. Created with -seglinkedit */
/* option to ld(1) for MH_EXECUTE and */
/* FVMLIB file types only */
複製代碼
#define SEG_UNIXSTACK "__UNIXSTACK" /* the unix stack segment */
複製代碼
#define SEG_IMPORT "__IMPORT" /* the segment for the self (dyld) */
/* modifing code stubs that has read, */
/* write and execute permissions */
複製代碼
符號表是Mach-O中的符號映射。
動態符號表是加載動態庫時的函數表,是符號表的子集。動態符號表的符號 = 符號在原所屬符號表中的offset + 原所屬符號表在動態符號表中的offset + 動態符號表的基地址base。在動態符號表中查找到的這個符號的值,又等於該符號在符號表中的offset。
使用otool來查看對main文件進行反彙編後的彙編代碼:
main:
(__TEXT,__text) section
_main:
0000000000000000 pushq %rbp
0000000000000001 movq %rsp, %rbp
0000000000000004 subq $0x20, %rsp
0000000000000008 movl $0x0, -0x4(%rbp)
000000000000000f movl %edi, -0x8(%rbp)
0000000000000012 movq %rsi, -0x10(%rbp)
0000000000000016 leaq 0x2e(%rip), %rdi
000000000000001d movb $0x0, %al
000000000000001f callq 0x24
0000000000000024 xorl %ecx, %ecx
0000000000000026 movl $0x1, -0x14(%rbp)
000000000000002d movl $0x2, -0x18(%rbp)
0000000000000034 movl -0x14(%rbp), %edx
0000000000000037 addl -0x18(%rbp), %edx
000000000000003a addl $0x3, %edx
000000000000003d movl %edx, -0x1c(%rbp)
0000000000000040 movl %eax, -0x20(%rbp)
0000000000000043 movl %ecx, %eax
0000000000000045 addq $0x20, %rsp
0000000000000049 popq %rbp
000000000000004a retq
複製代碼
在iOS 12.2/usr/include/mach-o/fat.h中:
struct fat_header {
uint32_t magic; /* FAT_MAGIC or FAT_MAGIC_64 */
uint32_t nfat_arch; /* number of structs that follow */
};
struct fat_arch_64 {
cpu_type_t cputype; /* cpu specifier (int) */
cpu_subtype_t cpusubtype; /* machine specifier (int) */
uint64_t offset; /* file offset to this object file */
uint64_t size; /* size of this object file */
uint32_t align; /* alignment as a power of 2 */
uint32_t reserved; /* reserved */
};
複製代碼
在iOS 12.2/usr/include/mach-o/loader.h
struct mach_header_64 {
uint32_t magic; /* mach magic number identifier */
cpu_type_t cputype; /* cpu specifier */
cpu_subtype_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 */
//表示二進制文件所支持的一些功能,和系統加載有關係。描述文件在編譯、連接等過程當中的信息,示例中的 MH_NOUNDEFS 表示文件中不存在未定義的符號,MH_DYLDLINK 表示文件要交由 DYLD 進一步處理,MH_TWOLEVEL 表示文件使用兩級命名空間,MH_PIE 表示啓用地址空間佈局隨機化。
uint32_t flags; /* flags */
uint32_t reserved; /* reserved */
};
複製代碼
header描述了Mach-O文件的基本信息,注意其中的CPU類型cputype,文件類型filetype,加載命令個數ncmds,全部加載命令的大小sizeofcmds。
不一樣的segment,會使用不一樣的加載命令。
struct dyld_info_command {
uint32_t cmd; /* LC_DYLD_INFO or LC_DYLD_INFO_ONLY */
uint32_t cmdsize; /* sizeof(struct dyld_info_command) */
uint32_t rebase_off; //重定向的偏移值。(ASLR)
uint32_t rebase_size; //重定向的大小。
uint32_t bind_off; //綁定。可執行文件讀到內存中會綁定一些數據。weak綁定,lazy綁定。
uint32_t bind_size; /* size of binding info */
uint32_t weak_bind_off; /* file offset to weak binding info */
uint32_t weak_bind_size; /* size of weak binding info */
uint32_t lazy_bind_off; /* file offset to lazy binding info */
uint32_t lazy_bind_size; /* size of lazy binding infs */
uint32_t export_off; //對外開發的函數。
uint32_t export_size; /* size of lazy binding infs */
};
複製代碼
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 */
};
複製代碼
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 */
};
複製代碼
在.app包內,執行命令查看可執行文件:
plutil -p Info.plist | grep CFBundleExecutable
# 結果 "CFBundleExecutable" => "XXXX"
複製代碼
class-dump即利用OC的runtime特性,將mach-o文件中的@interface和@prototol信息提取出來,生成對應的.h文件。
class-dump -H XXXX -o xxxx_headers
複製代碼
-H表示要生成頭文件,-o指定頭文件的存放目錄。
若是是Swift混編,則會出錯:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: [cursor offset] != 0'
libc++abi.dylib: terminating with uncaught exception of type NSException
複製代碼
從AppStore下載的App都是通過簽名加密的,class-dump會失敗。所以須要先砸殼,不然dump出來的頭文件是空的。 使用AppCrackr。
lipo是管理Fat File的工具,可查看平臺列表,提取特定平臺,從新打包。通常用於多架構mach-o文件的處理。
查看架構信息:
# lipo -info main
Non-fat file: main is architecture: x86_64
# lipo -info XXXX
Non-fat file: XXXX is architecture: arm64
複製代碼
查看到了該mach-o文件指定的平臺信息。若是有指定了多個平臺,則能夠提取單個平臺的mach-o文件。
提取特定架構:
lipo -thin armv7 MAMapKit -output MAMapKit.armv7
複製代碼
如單獨提取armv7結果的mach-o文件,體積要小不少。而多個mach-o之間會有共享的一些資源。
合併多個架構:
lipo -create WeChat_arm64 WeChat_armV7 -output WeChat_64_V7
複製代碼
lipo -detailed_info LuckyClient
複製代碼
等同於
otool -f -V LuckyClient
複製代碼
nm用來顯示一個程序包的符號表,默認會顯示入口地址、符號類型、符號名。
nm -j MAMapKit.armv7 | grep png > symbols
複製代碼
能夠得到全部的libpng導出符號,存入的symbols文件。-j只輸出符號名。
strip用來刪除程序裏的符號表。-R指定要刪除的符號列表。-S保留其餘符號。
strip -S -R symbols MAMapKit.armv7 -o MAMapKit.armv7.strip
複製代碼
查看mach-o特定部分(segment或者section)的內容。
otool -L XXXX
複製代碼
可查看當前App須要鏈接的全部framework。主要都是一些Apple的庫,以及swift相關。
otool能夠查看mach-o的信息:
-f fat headers
-a archive header
-h mach header
-l load commands
-L shared libraries used
-D shared library id name
-t text section (disassemble with -v)
-p <routine name> start disassemble from routine name
-s <segname> <sectname> contents of se
複製代碼
如:
# 查看頭文件
otool -v -h main
# 查看__TEXT __text
otool -s __TEXT __text main
# 查看__TEXT __cstring
otool -s __TEXT __cstring main
複製代碼
使用MachOView工具來查看Mach-O文件的具體細節,這裏就很少說了。
MachO-Kit是一個解析Mach-O文件的C語言庫。
其中使用了libMachO來進行Mach-O文件解析。
查看系統的全部動態庫:
find /usr/lib -name "*.dylib"
複製代碼
iOS系統常見的dylib有:
dyld是Apple的動態連接庫加載器,內核作好App的初始準備後,交個dyld負責。做用以下:
使用/usr/lib/dyld來加載動態庫,可加載dylib,bundle,execute等類型的文件。dyld會經過一個共享緩存來加速dylib的加載。
image即mach-o,裏邊是被編譯過的符號、代碼等,做用是將這些文件遞歸加載進內存,且每一個文件對應一個ImageLoader實例來負責加載。
因爲runtime向dyld綁定了回調,當image加載到內存後,dyld會通知runtime進行處理。 runtime接手後調用map_images作解析和處理,接下來load_images中調用call_load_methods方法,遍歷全部加載進來的Class,按繼承層級依次調用Class的+load方法和其Category的+load方法。
加載dylib文件
void *_revealLib;
_revealLib = dlopen([filePath cStringUsingEncoding:NSUTF8StringEncoding], RTLD_NOW);
// xxx
dlclose(_revealLib);
複製代碼
若是在+load方法中斷點,便可在Xcode中進行調試。查看調用堆棧:
+ (void)load方法
call_load_methods
_dyld_start
dyldbootstrap::start(macho_header const*, int, char const*, long, macho_header const*, unsigned long*)
複製代碼
call_load_methods中就能看到objc_autoreleasePoolPush與objc_autoreleasePoolPop。