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。