了解 Mach-O 文件

什么是 Mach-O

    Mach-O 其实是 Mach Object 文件格式的缩写,它是一种用于可执行文件、目标代码、动态库的文件格式,作为 a.out 格式的替代, Mach-O 提供了更强的扩展性。在 OS X 中,内核扩展、命令行工具、应用程序、框架和库(共享和静态)是使用 Mach-O 文件实现的。可以在 Mach-O Programming Topics 查看相关介绍。

分析 Mach-O 文件的工具

  • /usr/bin/lipo
    查看二进制文件信息,可以生成或者拆分多架构文件
  • /usr/bin/file
    显示文件的类型。对于多架构文件,它会显示构成存档的每个图像的类型。
  • /usr/bin/otool
    列出了 Mach-O 文件中特定部分和段的内容。它包括每个支持的体系结构的符号反汇编器,并且它知道如何格式化许多常见节类型的内容。
  • /usr/bin/pagestuff
    显示关于组成图像的每个逻辑页面的信息,包括每个页面中包含的部分和符号的名称。此工具不适用于包含多个架构的图像的二进制文件。
  • /usr/bin/nm
    允许查看目标文件符号表的内容。
  • MachoView.app
    MachoView.app

    在查看 Mach-O 文件之前,有必要了解MachOView.app 工具,MachOView工具属于免费开源项目,源代码可在Github-MachOView下载,可以在 Mac 中查看 Mach-O文件的详细信息

Mach-O 文件结构

一个 Mach-O 文件包含三个主要区域(如下所示):

  • Header:指定文件的目标架构
  • Load commands :指定文件的逻辑结构和文件在虚拟内存中的布局。
  • Raw segment data:包含加载命令中定义的段的原始数据。
Apple Mach-O 文件结构

当然,可以通过 MachOView 对 App 可执行文件进行查看,Mach-O 文件里面的内容如下图所示:

Mach-O 文件结构

Mach64 Header

每个 Mach-O 文件的开头都有一个 Header ,用于将文件标识为 Mach-O 文件。Header 包含该二进制文件的一般信息。字节顺序、架构类型、加载指令的数量等。使得可以快速确认一些信息,比如当前文件用于32位还是64位,对应的处理器是什么、文件类型是什么

Mach64 Header 结构

在 macho/loader.h 中,可以查看到 Mach-O header 信息结构代码如下:


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 的数量
    uint32_t        sizeofcmds; // load commands 大小
    uint32_t        flags;      // 标签
    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 的数量和大小等文件信息
  • sizeofcmds
    load commands 大小
  • flags
    标志位,标识二进制文件支持的功能。
  • reserved
    保留字段

Mach-O 文件类型

其中,文件类型 filetype 表示了当前 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. */

其中,部分类型是我们开发中常用的:

  • OBJECT,指的是 .o 文件或者 .a 文件;
  • EXECUTE,指的是 IPA 拆包后的可执行文件;
  • DYLIB,指的是 .dylib 或 .framework 文件;
  • DYLINKER,指的是动态链接器;
  • DSYM,指的是保存有符号信息用于分析闪退信息的文件。

Load Commands

紧随 Header 的是一系列的 Load Commands ,负责描述文件在虚拟内存中逻辑结构和布局,这些加载指令清晰地告诉加载器如何处理二进制数据,有些命令是由内核处理的,有些是由动态链接器处理的。除了其他信息,Load Commands 可以指定:

  • 文件在虚拟内存中的初始布局

  • 符号表的位置(用于动态链接)

  • 程序主线程的初始执行状态

  • 包含主可执行文件的导入符号定义的共享库的名称

Load Commands结构

macho/loader.h 中,可以查看到 load_command 信息结构代码如下:

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 */

通过 MachOView 来继续查看 LoadCommands 内容

MachOView 中查看的 LoadCommands

对应的描述如下表格:

load commands Type 描述
LG_SEGMENT_64 将文件中(32位或64位)的段映射到进程地址空间中
LC_DYLD_INFO_ONLY 动态链接相关信息 rebase、bind
LC_SYMTAB 符号表
LC_DYSYMTAB 动态符号表
LC_UUID 文件的 UUID
LC_VERSION_MIN_IPHONES 支持最低的操作系统版本
LC_SOURCE_VERSION 源代码版本
LC_MAIN 程序入口地址和栈大小
LC_LOAD_DYLIB 依赖系统库路径
LC_RPATH 运行时优先搜索依赖库的目录路径
LC_FUNCTION_STARTS 函数起始地址表
LC_CODE_SIGNATURE 代码签名

Segment_Command

LC_SEGMENT_64LC_SEGMENT 是加载的主要命令,它负责指导内核来设置进程的内存空间,下面是

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 作为可执行文件的第一个段。该段位于虚拟内存位置 0 并且没有分配保护权限。该__PAGEZERO段是当前体系结构的一个完整 VM 页面的大小。因为 __PAGEZERO 段中没有数据,所以不占用文件中的空间
    • __TEXT 对应的就是代码段
    • __DATA 对应的是可读/可写的数据
    • __OBJC 包含由 Objective-C 语言运行时支持库使用的数据
    • __LINKEDIT 是支持dyld的,里面包含一些符号表等数据
    • __IMPORT 包含符号存根和指向未在可执行文件中定义的符号的非惰性指针。此段仅为针对 IA-32 架构的可执行文件生成。
  • VM Address
    段的虚拟内存地址
  • VM Size
    段的虚拟内存大小
  • file offset
    段在文件中偏移量
  • file size:段在文件中的大小
    将该段对应的文件内容加载到内存中:从offset处加载 file size大小到虚拟内存 vmaddr处
  • nsects
    标示了Segment中有多少secetion

通过 MachOView 查看 TEXT 段 内容

MachOView 中 LG_SEGMENT_64(_TEXT)

Raw segment data

Load Commands 之后,所有的 Mach-O 文件都包含一个或多个 Segment 的数据。Segment 的每个部分都包含某种特定类型的代码或数据,每个 Segment 定义了一个虚拟内存区域,动态链接器将其映射到进程的地址空间。Segments 和 Sections 的确切数量和布局由加载命令和文件类型指定

Section_64 结构

macho/loader.h 中,可以查看到 section_64 信息结构代码如下:

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(数据段)
  • addr
    该 section在内存的起始位置
  • size
    该 section 的大小
  • offset
    该 section 的文件偏移
  • align
    字节大小对齐
  • reloff
    重定位入口的文件偏移
  • nreloc
    需要重定位的入口数量
  • flags
    包含 section 的 type 和 attributes

Section 的类型

名称 描述 所属部分
__TEXT,__text 可执行机器码。编译器通常只在此部分中放置可执行代码,而不放置任何类型的表或数据。 __TEXT
__TEXT,__cstring 常量 C 字符串。静态链接器在构建最终产品时合并常量 C 字符串值,删除重复项。 __TEXT
__TEXT,__picsymbol_stub 与位置无关的间接符号存根 __TEXT
__TEXT,__symbol_stub 间接符号存根。 __TEXT
__TEXT,__const 初始化常量。 __TEXT
__TEXT,__literal4 4 字节字面量 __TEXT
__TEXT,__literal8 8 字节字面量 __TEXT
__DATA,__data 已初始化的可变变量 __DATA
__DATA,__la_symbol_ptr 懒加载符号指针 __DATA
__DATA,__nl_symbol_ptr 非懒加载符号指针 __DATA
__DATA,__dyld 动态链接器使用的占位符部分 __DATA
__DATA,__const 初始化的可重定位常量 __DATA
__DATA,__mod_init_func 静态构造函数 __DATA
__DATA,__mod_term_func 模块终止功能 __DATA
__DATA,__bss 未初始化静态变量的数据 __DATA
__DATA,__common 位于全局范围内的变量声明 __DATA

你可能感兴趣的:(了解 Mach-O 文件)