可执行文件 Mach-O

Mach-O 类型的文件:是一种用于可执行文件、目标代码、动态库、内核转储的文件格式;

使用工具 MachOView 查看 Mach-O 文件结构

Mach-O 文件的大致结构.png

分析上图可知,Mach-O 文件主要包含三个区域:

  • 1、mach_header_64 头部: 描述了 Mach-O 的CPU架构、文件类型以及加载命令等信息。
  • 2、Load Commands 加载命令: 描述了文件中数据的具体组织结构,不同的数据类型使用不同的加载命令表示
  • 3、Data: load_command 中定义的原始数据

1、头部mach_header_64

在 苹果源码 XNU 中的 loader.h 中找到结构mach_headermach_header_64 分别描述 32 位与 64 位的 Mach-O 头部信息。

struct mach_header {
    uint32_t    magic;// Mach-O 文件支持设备的CPU位数, 32位 CPU 取值 oxFEEDFACE 
    cpu_type_t  cputype;// CPU类型
    cpu_subtype_t   cpusubtype; // CPU 子类型
    uint32_t    filetype;   //文件类型,比如可执行文件、库文件、Dsym文件;
    uint32_t    ncmds;      //加载命令的数量
    uint32_t    sizeofcmds; //所有加载命令的大小
    uint32_t    flags;      //dyld 加载所需的标记:MH_PIE 表示启动地址空间布局随机化
};

struct mach_header_64 {
    uint32_t    magic;// Mach-O 文件支持设备的CPU位数,64位取值 xFEEDFACF
    cpu_type_t  cputype;    // CPU类型
    cpu_subtype_t   cpusubtype;// CPU 子类型
    uint32_t    filetype;   //文件类型,比如可执行文件、库文件、Dsym文件;
    uint32_t    ncmds;      //加载命令的数量
    uint32_t    sizeofcmds; //所有加载命令的大小
    uint32_t    flags;      //dyld 加载所需的标记:MH_PIE 表示启动地址空间布局随机化
    uint32_t    reserved;   //64 位的保留字段
};
1.1、结构成员 magic

结构成员 magic表示 Mach-O 文件支持设备的CPU位数, XNU 中有预定义的常量:

// mach_header(32位) 的 magic 字段的常量
#define MH_MAGIC    0xfeedface // 表示32位二进制
#define MH_CIGAM    NXSwapInt(MH_MAGIC)

//mach_header_64(64位) 的 magic 字段的常量
#define MH_MAGIC_64 0xfeedfacf //表示64位二进制
#define MH_CIGAM_64 NXSwapInt(MH_MAGIC_64)

上述四个常量很有用处,用于判断当前可执行文件能否支持设备的 CPU:比如 Runtime 库的 objc-os.mm文件中函数 bad_magic()

bool bad_magic(const headerType *mhdr){
    return (mhdr->magic != MH_MAGIC  &&  mhdr->magic != MH_MAGIC_64  &&
            mhdr->magic != MH_CIGAM  &&  mhdr->magic != MH_CIGAM_64);
}

该函数判断当前可执行文件,如果既不支持 32 位 CPU,又不支持 64 位 CPU,则返回 YES

1.2、结构成员 filetype

结构成员 filetype表示 Mach-O 文件的类型,关于它的常量有:

#define MH_OBJECT   0x1 //可重定位目标文件:.o文件 .a/.framework静态库
#define MH_EXECUTE  0x2 //请求分页的可执行文件: app/MyApp ; .out
#define MH_FVMLIB   0x3 //固定VM共享库文件
#define MH_CORE     0x4 //核心文件
#define MH_PRELOAD  0x5 //预加载可执行文件
#define MH_DYLIB    0x6 //动态库 .framework  .dylib
#define MH_DYLINKER 0x7 //动态链接器 usr/lib/dyld
#define MH_BUNDLE   0x8 //动态绑定 Bundle 文件

在本文,笔者分析的就是 .app 文件,通过运行一个 Demo 得到!从 Mach-O 文件的大致结构 图 可以看到该文件 .app 文件的类型就是 MH_EXECUTE

1.2.1、.app 文件

苹果是如何将一个.app文件加载到 dyld2 呢?笔者在 dyld2 库 的 ImageLoaderMachO.cpp 文件中发现了函数 ImageLoaderMachO()

// 为主程序创建镜像
ImageLoaderMachO::ImageLoaderMachO(const struct mach_header* mh, uintptr_t slide, const char* path, const LinkContext& context)
 : ImageLoader(path)
{
    this->init();
    fMachOData = (const uint8_t*)mh;
    this->instantiateSegments((const uint8_t*)mh);
    this->setSlide(slide);
    this->parseLoadCmds();
    this->adjustSegments();
#if __i386__
    if ( fReadOnlyImportSegment != NULL )
        fReadOnlyImportSegment->tempWritable(context, this);
#endif  
    if ( mh->flags & MH_PIE )
        Segment::fgNextPIEDylibAddress = (uintptr_t)this->getEnd(); 
    this->setMapped(context);
    if ( context.verboseMapping ) {
        dyld::log("dyld: Main executable mapped %s\n", this->getPath());
        for (ImageLoader::SegmentIterator it = this->beginSegments(); it != this->endSegments(); ++it ) {
            Segment* seg = *it;
            if ( (strcmp(seg->getName(), "__PAGEZERO") == 0) || (strcmp(seg->getName(), "__UNIXSTACK") == 0)  )
                dyld::log("%18s at 0x%08lX->0x%08lX\n", seg->getName(), seg->getPreferredLoadAddress(), seg->getPreferredLoadAddress()+seg->getSize());
            else
                dyld::log("%18s at 0x%08lX->0x%08lX\n", seg->getName(), seg->getActualLoadAddress(this), seg->getActualLoadAddress(this)+seg->getSize());
        }
    }
}

此处笔者不对该函数做过多解释,以免跑题! 读者需要知道的就是:本文分析的 Mach-O 文件就是为了在 dyld2 中加载的!

1.2.2、MH_BUNDLE 文件

MH_BUNDLE 类型的文件 是不能加载到共享缓存之中的,所以在加载Mach-O 文件时 Runtime的 objc-runtime-new.mm 文件中函数 mustReadClasses(),会被调用来检查是否有 Bundle类:

bool mustReadClasses(header_info *hi){
    const char *reason;
    if (!hi->isPreoptimized()) {
        reason = nil;
        goto readthem;
    }    
#if TARGET_OS_SIMULATOR
    reason = "the image is for iOS simulator";
    goto readthem;
#endif
    assert(!hi->isBundle());  // no MH_BUNDLE in shared cache    
    if (!noMissingWeakSuperclasses()) {
        reason = "the image may contain classes with missing weak superclasses";
        goto readthem;
    }    
    if (haveFutureNamedClasses()) {
        reason = "there are unresolved future classes pending";
        goto readthem;
    }    
    return NO;
readthem:
    if (PrintPreopt  &&  reason) {
        _objc_inform("PREOPTIMIZATION: reading classes manually from %s because %s", hi->fname(), reason);
    }
    return YES;
}

我们可以看到该函数的关键代码 assert(!hi->isBundle()) ,如果是MH_BUNDLE类型文件,则无法通过断言!确保 Bundle 类不会被放到共享缓存!

1.3、结构成员 flags

结构成员 flags有以下几个常量

#define MH_NOUNDEFS 0x1 //目标文件没有未定义的引用,可以执行
#define MH_INCRLINK 0x2  //目标文件是针对基本文件的增量链接的输出,不能再次链接编辑
#define MH_DYLDLINK 0x4 //目标文件是动态链接器的输入,不能再次静态链接编辑
#define MH_BINDATLOAD   0x8 //加载时,目标文件的未定义引用由动态链接器绑定。
#define MH_PREBOUND 0x10 //该文件具有预先绑定的动态未定义引用。
1.4、结构成员 ncmdssizeofcmds

由 Mach-O 文件的大致结构 图 可以看到:

  • load_command 紧跟mach_header_64 之后;
  • 图中 Size of load Commands的取值是 3496,该值是结构mach_header_64 的成员sizeofcmds,表示所有加载命令load_command所占内存之和;
  • 图中 Number of load Commands的取值是 21,该值是结构mach_header_64 的成员ncmds,表示所有加载命令load_command的个数;
ncmds 与 sizeofcmds.png

2、加载命令部分Load commands

Load commands紧跟mach_header_64 之后;告诉操作系统应当如何加载文件中的数据,对系统内核加载器和动态链接器起指导作用。
一个 Mach-O 文件中有多个加载命令Load commands,当然这些load_command也具有不同的类型!

load_command 分析.png
2.1、加载命令load_command

无论查看哪种类型的load_command,我们都可以发现前两个字段总是cmdcmdsize,如上图所示:相同的 cmd下它们的结构成员也是相同的!

struct load_command {
    unsigned long cmd;
    unsigned long cmdsize;
};
  • 结构成员cmd表示该条加载命令的类型,每种 load_command 类型都有专门针对它的结构;
  • 结构成员cmdsize表示load_command 的总大小:结构体大小 + 加上包含的节、字符串等;

要前进到下一个 load_command,可以将 cmdsize 添加到当前 load_command 的偏移量或指针: 32位的 cmdsize 必须是4个字节的倍数,64位的 cmdsize ,必须是8个字节的倍数;永远是 load_command 的最大对齐:sizeof(long)
填充字节必须为零。

目标文件中的所有表也必须遵循这些规则,以便文件可以进行内存映射;否则,指向这些表的指针在某些 CPU 将无法正常工作或根本无法正常工作。所有填充为 0 的对象将逐个字节进行比较。

2.2.1、加载命令load_command的类型

在上图可以看到不同的 cmd下它们的结构成员大不相同,那么都有哪些不同的类型呢?苹果为 load_command 的类型定义了一些常量:

#define LC_SEGMENT  0x1 //该文件被映射的段
#define LC_SYMTAB   0x2 //为文件定义符号表和字符串表,在连接文件时被链接器使用,同时也用于调试器映射符号到源文件。符号表定义的本地符号仅用于本地测试,而已定义和未定义的 external 符号被链接器使用
#define LC_SYMSEG   0x3 //符号表信息,符号表中详细说明了代码中所用符号的信息等(过时)
#define LC_THREAD   0x4 //线程
#define LC_UNIXTHREAD   0x5 //unix线程(包括堆栈)
#define LC_LOADFVMLIB   0x6 //加载指定的固定VM共享库
#define LC_IDFVMLIB 0x7 //固定VM共享库的标识
#define LC_IDENT    0x8 //object 标识信息(已过时)
#define LC_FVMFILE  0x9 /* fixed VM file inclusion (internal use) */
#define LC_PREPAGE      0xa     //prepage 命令(内部使用)
#define LC_DYSYMTAB 0xb //将符号表中给出符号的额外符号信息提供给动态链接器
#define LC_LOAD_DYLIB   0xc //依赖的动态库,包括动态库名称、当前版本号、兼容版本号,(可以使用 otool -L xxx 命令查看)
#define LC_ID_DYLIB 0xd //动态链接共享库的标识
#define LC_LOAD_DYLINKER 0xe //默认的加载器路径
#define LC_ID_DYLINKER  0xf //动态链接器识别
#define LC_PREBOUND_DYLIB 0x10  /* modules prebound for a dynamicly */
LC_SEGMENT :段的映射

LC_SEGMENT类型的加载命令是最常见的 load_command 了,该命令被映射到段segment_command_64,关于段的详细讲解,笔者放到下一节再提及!

2.2、段segment_command_64

先在 XNU 中的 loader.h 中找到结构segment_command_64的实现:

struct segment_command {// 32 位
    unsigned long   cmd;//load_command结构成员cmd的取值,取值 LC_SEGMENT 将文件中的段映射到进程地址空间
    unsigned long   cmdsize;//load_command结构大小
    char        segname[16];// 16字节的段名字
    unsigned long   vmaddr; //映射到虚拟地址的偏移
    unsigned long   vmsize; //映射到虚拟地址的大小
    unsigned long   fileoff;//对应于当前架构文件的偏移(注意:是当前架构,不是整个 FAT 文件)
    unsigned long   filesize;//文件大小
    vm_prot_t   maxprot;//段里面的最高内存保护
    vm_prot_t   initprot;//初始内存保护
    unsigned long   nsects;//该段包含的节个数
    unsigned long   flags;//段页面标志
};

struct segment_command_64 {//64 位
    uint32_t    cmd;//load_command结构成员cmd的取值,取值 LC_SEGMENT 将文件中的段映射到进程地址空间
    uint32_t    cmdsize;//load_command结构大小
    char        segname[16];// 16字节的段名字
    uint64_t    vmaddr; //映射到虚拟地址的偏移
    uint64_t    vmsize; //映射到虚拟地址的大小
    uint64_t    fileoff;//对应于当前架构文件的偏移(注意:是当前架构,不是整个 FAT 文件)
    uint64_t    filesize;//文件大小
    vm_prot_t   maxprot;//段里面的最高内存保护
    vm_prot_t   initprot;//初始内存保护
    uint32_t    nsects;//该段包含的节个数
    uint32_t    flags;//段页面标志 : 表示节的标志
};

通过对比第2节的 load_command 分析图可以看出,LC_SEGMENT类型的load_command中的结构成员对应着段segment_command_64的成员,其中:

segment_command_64的结构成员 cmd 取值一定为 LC_SEGMENT,表示是段命令,该cmd 也对应着load_command中的cmd
cmdsize 也是如此!

2.2.1、结构成员segname

结构成员segname将段分为不同类型的段,在第2节的 load_command 分析图中,可以看到相同LC_SEGMENT类型的load_command有四个,但是这四个load_commandsegname不同。

结构成员segname代表该段的名字,是一个 16字节的字符串!苹果为其提供了几个常量

//空指针陷阱段,映射到虚拟内存空间的第 1 页,用于捕捉 MH_EXECUTE 文件对 NULL 指针的引用
#define SEG_PAGEZERO "__PAGEZERO" 

#define SEG_TEXT    "__TEXT" //传统 UNIX 代码段,只读不可写
#define SEG_DATA    "__DATA" //读取和写入数据的程序数据段
#define SEG_OBJC    "__OBJC"    //objective-C runtime segment
#define SEG_ICON     "__ICON"   //icon segment
#define SEG_LINKEDIT    "__LINKEDIT" //由链接编辑器创建和维护的所有结构的段,仅为 MH_EXECUTE 和 FVMLIB 类型的文件使用 ld(1) 的- seglinkedit 选项创建
#define SEG_UNIXSTACK   "__UNIXSTACK" //unix堆段
segment_command_64 分析.png

在不同segname的段中,还有一堆更细的分类,这些细致分类是一个个的节section_64 !不同的segname各自存储着它们自己的节section_64

a、__TEXT

__TEXT段中常见的节类型:

__text //程序可执行的代码区域
__stubs //间接符号存根,跳转到懒加载指针表
__stub_helper //帮助解决懒加载符号加载的辅助函数
__objc_methname //OC方法名称
__objc_methtype //OC方法签名
__objc_classname //OC类名
__cstring //只读的 C 字符串,包含 OC 的部分字符串和属性名
__const //常量
b、__DATA

__DATA段中常见的节类型

__nl_symbol_ptr //非懒加载指针表,在 dyld  加载时会立即绑定值
__la_symbol_ptr //懒加载指针表,第一次调用时才会绑定值
__got // 非懒加载全局指针表
__mod_init_func // constructor 函数
__mod_term_func // destructor 函数
__cfstring //OC 字符串
__objc_classlist //文件中类的列表
__objc_nlclslist //文件中自己实现了 +load 方法的类
__objc_protolist //协议列表
__objc_classrefs // 被引用的类列表
__objc_imageinfo // OC镜像信息
__objc_protollist //OC原型列表
__objc_const //OC常量
__objc_selfrefs //OC类自引用(self)
__objc_superrefs //OC类超类引用(super)
__objc_protolrefs // OC原型引用
__objc_selrefs //被引用SEL对应的字符串
__objc_msgrefs //
__objc_nlclslist//获取非懒加载的所有的类的列表
__objc_catlist//获取文件中的 category
__objc_nlcatlist //获取非懒加载的所有的分类的列表
__objc_protorefs //OC 协议引用
__objc_init_func //
__bss //没有初始化和初始化为0 的全局变量
__data //真正初始化的数据节没有填充,没有bss重叠
__common //链接编辑器在节中分配公共符号
2.3、节 section_64

说了这么多的节类型,我们还是先看看节 section_64的具体实现吧:

struct section {// 32 位
    char        sectname[16];//节的名字
    char        segname[16];//节所在段的名字
    unsigned long   addr;//映射到虚拟地址的偏移
    unsigned long   size;//节的大小
    unsigned long   offset;//节在当前架构文件中的偏移
    unsigned long   align;//节的字节对齐大小 n ,计算结果为 2^n
    unsigned long   reloff;//重定位入口的文件偏移
    unsigned long   nreloc; //重定位入口的个数
    unsigned long   flags;//节的类型和属性
    unsigned long   reserved1;  //保留位
    unsigned long   reserved2;  //保留位
};

struct section_64 { // 64 位
    char        sectname[16];//节的名字
    char        segname[16];//节所在段的名字
    uint64_t    addr;//映射到虚拟地址的偏移
    uint64_t    size;//节的大小
    uint32_t    offset;//节在当前架构文件中的偏移
    uint32_t    align;  //节的字节对齐大小 n ,计算结果为 2^n
    uint32_t    reloff;//重定位入口的文件偏移
    uint32_t    nreloc; //重定位入口的个数
    uint32_t    flags;//节的类型和属性
    uint32_t    reserved1;  //用于偏移量或索引
    uint32_t    reserved2;  //数量或大小
    uint32_t    reserved3;//保留位
};

笔者在上节分析的那么多的节类型,就是 section_64的结构成员sectname,不同的节名sectname代表不同的含义!

2.3.1、节 section_64flags
/* section_64 结构的flags字段表示属性部分的常量
 */
#define SECTION_ATTRIBUTES_USR   0xff000000 /* User setable attributes */
#define S_ATTR_PURE_INSTRUCTIONS 0x80000000 /* section contains only true machine instructions */
#define SECTION_ATTRIBUTES_SYS   0x00ffff00 /* system setable attributes */
#define S_ATTR_SOME_INSTRUCTIONS 0x00000400 /* section contains some machine instructions */
#define S_ATTR_EXT_RELOC     0x00000200 /* section has external relocation entries */
#define S_ATTR_LOC_RELOC     0x00000100 /* section has local relocation entries */
2.3.2、虚拟地址
虚拟地址.png

虚拟地址section_64-> addr,表示该节的内容映射到虚拟内存时相对该模块加载基地址的偏移。
运行程序,获取该模块加载基地址 (该地址每次运行程序,都是不同的),然后加上偏移的虚拟地址:

(lldb) im li -o -f RuntimeUseDemo
[  0] 0x0000000001584000 /Users/longlong/Library/Developer/Xcode/DerivedData/SourceCode-gbviaxaumpwhkieshjwwgrxrxpva/Build/Products/Debug-iphonesimulator/RuntimeUseDemo.app/RuntimeUseDemo
(lldb) p/x 0x00000001000019C0 + 0x0000000001584000
(long) $0 = 0x00000001015859c0

0x00000001015859c0这个地址反汇编:

(lldb) dis -a 0x00000001015859c0
RuntimeUseDemo`main:
    0x1015859c0 <+0>:   pushq  %rbp
    0x1015859c1 <+1>:   movq   %rsp, %rbp
    0x1015859c4 <+4>:   subq   $0x40, %rsp
    0x1015859c8 <+8>:   movl   $0x0, -0x4(%rbp)
    0x1015859cf <+15>:  movl   %edi, -0x8(%rbp)
    0x1015859d2 <+18>:  movq   %rsi, -0x10(%rbp)
    0x1015859d6 <+22>:  callq  0x10158a46e               ; symbol stub for: objc_autoreleasePoolPush
    0x1015859db <+27>:  leaq   0x7906(%rip), %rsi        ; @"RW_REALIZED|RW_REALIZING -------- %x"
    0x1015859e2 <+34>:  movl   $0x80080000, %edi         ; imm = 0x80080000 
    0x1015859e7 <+39>:  movl   %edi, -0x14(%rbp)
    0x1015859ea <+42>:  movq   %rsi, %rdi
    0x1015859ed <+45>:  movl   -0x14(%rbp), %esi
    0x1015859f0 <+48>:  movq   %rax, -0x20(%rbp)
    0x1015859f4 <+52>:  movb   $0x0, %al
    0x1015859f6 <+54>:  callq  0x10158a39c               ; symbol stub for: NSLog
    0x1015859fb <+59>:  leaq   0x7906(%rip), %rdi        ; @"RO_META-------- %x"
    0x101585a02 <+66>:  movl   $0x1, %esi
    0x101585a07 <+71>:  movb   $0x0, %al
    0x101585a09 <+73>:  callq  0x10158a39c               ; symbol stub for: NSLog
    0x101585a0e <+78>:  leaq   0x7913(%rip), %rdi        ; @"RO_ROOT-------- %x"
    0x101585a15 <+85>:  movl   $0x2, %esi
    0x101585a1a <+90>:  movb   $0x0, %al
    0x101585a1c <+92>:  callq  0x10158a39c               ; symbol stub for: NSLog
    0x101585a21 <+97>:  leaq   0x7920(%rip), %rdi        ; @"RW_REALIZED-------- %x"
    0x101585a28 <+104>: movl   $0x80000000, %esi         ; imm = 0x80000000 
    0x101585a2d <+109>: movb   $0x0, %al
    0x101585a2f <+111>: callq  0x10158a39c               ; symbol stub for: NSLog
    0x101585a34 <+116>: leaq   0x792d(%rip), %rdi        ; @"-------- %lx"
    0x101585a3b <+123>: movl   $0x1e08, %esi             ; imm = 0x1E08 
    0x101585a40 <+128>: movb   $0x0, %al
    0x101585a42 <+130>: callq  0x10158a39c               ; symbol stub for: NSLog
    0x101585a47 <+135>: movl   -0x8(%rbp), %edi
    0x101585a4a <+138>: movq   -0x10(%rbp), %rsi
    0x101585a4e <+142>: movq   0x9b63(%rip), %rcx        ; (void *)0x000000010158f6a0: AppDelegate
    0x101585a55 <+149>: movq   0x989c(%rip), %rdx        ; "class"
    0x101585a5c <+156>: movl   %edi, -0x24(%rbp)
    0x101585a5f <+159>: movq   %rcx, %rdi
    0x101585a62 <+162>: movq   %rsi, -0x30(%rbp)
    0x101585a66 <+166>: movq   %rdx, %rsi
    0x101585a69 <+169>: callq  *0x7601(%rip)             ; (void *)0x0000000101ec6d80: objc_msgSend
    0x101585a6f <+175>: movq   %rax, %rdi
    0x101585a72 <+178>: callq  0x10158a3a8               ; symbol stub for: NSStringFromClass
    0x101585a77 <+183>: movq   %rax, %rdi
    0x101585a7a <+186>: callq  0x10158a4d4               ; symbol stub for: objc_retainAutoreleasedReturnValue
    0x101585a7f <+191>: xorl   %r8d, %r8d
    0x101585a82 <+194>: movl   %r8d, %edx
    0x101585a85 <+197>: movl   -0x24(%rbp), %edi
    0x101585a88 <+200>: movq   -0x30(%rbp), %rsi
    0x101585a8c <+204>: movq   %rax, %rcx
    0x101585a8f <+207>: movq   %rax, -0x38(%rbp)
    0x101585a93 <+211>: callq  0x10158a3b4               ; symbol stub for: UIApplicationMain
    0x101585a98 <+216>: movl   %eax, -0x4(%rbp)
    0x101585a9b <+219>: movq   -0x38(%rbp), %rcx
    0x101585a9f <+223>: movq   %rcx, %rdi
    0x101585aa2 <+226>: callq  *0x75d8(%rip)             ; (void *)0x0000000101ec4010: objc_release
    0x101585aa8 <+232>: movq   -0x20(%rbp), %rdi
    0x101585aac <+236>: callq  0x10158a468               ; symbol stub for: objc_autoreleasePoolPop
    0x101585ab1 <+241>: movl   -0x4(%rbp), %eax
    0x101585ab4 <+244>: addq   $0x40, %rsp
    0x101585ab8 <+248>: popq   %rbp
    0x101585ab9 <+249>: retq   

使用 Xocde 的 Debug -> DebugWorkflow -> ViewMemory ,输入地址0x00000001015859c0

Xcode 查看指定地址在文件中的内容.png

而我们使用MachOView 查看文件偏移 0x000019C0处的数据如下图所示:

使用MachOView查看.png

可以看到,这两处看到的内容是一样的!

3、数据部分

我们分析了 Mach-O 的头部mach_header_64头部,知道了 Mach-O 的CPU架构、文件类型以及加载命令等信息;接着又分析了加载命令Load Commands ,知道了 应当如何加载文件中的数据。

我们的目的是为了读取 Mach-O 的信息,这些信息就存储在 Mach-O 的数据部分,接下来,我们尝试去解析一些数据!

文件偏移地址 = 虚拟地址 - 模块在内存中地址 + 模块在文件中的偏移

你可能感兴趣的:(可执行文件 Mach-O)