iOS底层-Mach-O探索(二)

1,034 阅读5分钟

Mach-O探索(一)”我们已经分析过了Mach-O到底是什么,包括分析Mach-O有什么用,以及分析Mach-O常用工具。 现在我们分析一下Mach-O文件的结构及它们的功能。

Mach-O的格式

对于苹果来说,Mach-O是可执行文件,它其实包含以下几种文件类型

  • Executable:可执行文件
  • Dylib:动态链接库
  • Bundle:无法被链接的动态库,只能在运行时使用dlopen加载
  • Image:指的是ExecutableDylibBundle的一种
  • Framework:包含Dylib、资源文件和头文件的集合 通过machoview我们可以得到其内部结构 归纳以下Mach-O文件组成如下图 总结:一个Mach-O的组成主要分为三大部分:Header Mach-O头部Load Commands 加载命令Data 数据。它们的作用如下:
  • Header Mach-O头部:主要是Mach-O的cpu架构,文件类型以及加载命令等信息
  • Load Commands 加载命令:描述了文件内部数据的具体组织结构,不同的数据类型使用不同的加载命令表示
  • Data 数据:数据中的每个段(segment)的数据都保存在这里。每个段都有一个或多个部分,它们放置了具体的数据与代码,主要包含代码,数据,例如符号表,动态符号表等等

Header分析

Header源码分析如下

struct mach_header_64 {
    // 0xfeedface(32位) 0xfeedfacf(64位),系统内核用来判断是否是mach-o格式
    uint32_t    magic;      /* mach magic number identifier */
    // CPU类型,比如ARM
    cpu_type_t  cputype;    /* cpu specifier */
    // CPU的具体类型,例如arm64、armv7
    cpu_subtype_t   cpusubtype; /* machine specifier */
    // 由于可执行文件、目标文件、静态库和动态库等都是mach-o格式,filetype来说明mach-o文件是属于哪种文件
    uint32_t    filetype;   /* type of file */
    // 加载命令的条数(加载命令紧跟header之后)
    uint32_t    ncmds;      /* number of load commands */
    // 加载命令的大小
    uint32_t    sizeofcmds; /* the size of all the load commands */
    // 标志位标识二进制文件支持的功能,主要是和系统加载、链接有关
    uint32_t    flags;      /* flags */
    // 保留字段
    uint32_t    reserved;   /* reserved */
};

CPU通过读取Header文件能快速获取到可执行文件支持的CPU架构、系统类型、指令的条数。针对32位和64位架构的cpu,分别使用了mach_headermach_header_64结构体。64位架构的mach_header_6432位架构的mach_header,仅仅多了一个reserved保留字段。

Load Commands

Load Commands主要是用于加载指令,它的大小和数目在Header中已经被提供,Load Commands源码分析如下

struct load_command {
    // 命令的类型
    uint32_t cmd;       /* type of load command */
    // 命令的大小
    uint32_t cmdsize;   /* total size of command in bytes */
};

Load commands记录了动态链接器的位置、程序的入口、依赖库的信息、代码的位置、符号表的位置等等信息。Load commands内部组成部分功能如下:
LC_SEGMENT_64(_PAGEZERO):用于记录共享虚拟空间信息如位置和大小,一般用于放空指针
LC_SEGMENT_64(_TEXT):只读数据段,记录TEXT段的起始位置、大小、偏移信息。
LC_SEGMENT_64(_DATA):读写数据段,记录DATA段的起始位置、大小、偏移信息。
LC_SEGMENT_64(_LINKEDIT):链接使用段,记录dyld的位置信息。
LC_DYLD_INFO_ONLY:记录dyld的重定向、懒加载、绑定等信息。
LC_SYMTAB:符号表信息,记录符号表的位置、偏移量、数据个数等。
LC_DYSYMTAB:符号表的一些额外信息。
LC_LOAD_DYLINKER:记录dyld链接的cpu的内核信息,和位置信息。
LC_UUID:唯一标识符。
LC_SOURCE_VERSION:代码的版本信息。
LC_MAIN:入口地址,dyld通过这里跳转程序主入口。
LC_ENCRYPTION_INFO_64:加密标识,标记了是否被加密以及加密内容的偏移等。 LC_LOAD_DYLIB(Foudation):依赖库信息,标记了依赖库的位置和版本信息。
LC_RPATH:路径信息。
LC_FUCTION_STARTS:函数起始地址表。
LC_DATA_IN_CODE : 代码段非指令表。
LC_CODE_SIGNATURE:代码签名信息。 其中LC_SEGMENT_64的类型segment_command_64源码定义如下

   // 段加载命令
struct segment_command_64 { /* for 64-bit architectures */
    // 表示加载命令类型,
    uint32_t    cmd;        /* LC_SEGMENT_64 */
    // 表示加载命令大小(还包括了紧跟其后的nsects个section的大小)
    uint32_t    cmdsize;    /* includes sizeof section_64 structs */
    // 16个字节的段名字
    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 */
    // 段页面所需要的最高内存保护(4 = r,2 = w,1 = x)
    vm_prot_t   maxprot;    /* maximum VM protection */
    // 段页面初始的内存保护
    vm_prot_t   initprot;   /* initial VM protection */
    // 段中section数量
    uint32_t    nsects;     /* number of sections in segment */
    // 杂项标志位
    uint32_t    flags;      /* flags */
};

DATA

data区域存储了代码信息,而且此区域可读可写。如方法、符号表、字符表、代码数据、连接器所需的数据(重定向、符号绑定等)。在data区域,section占用了很大的比例。SectionMach.h中是以section_64(在arm64架构下)表示,其源码定义如下

struct section_64 { /* for 64-bit architectures */
    // 当前section的名称
    char        sectname[16];   /* name of this section */
    // section所在的segment名称
    char        segname[16];    /* segment this section goes in */
    // 内存中起始位置
    uint64_t    addr;       /* memory address of this section */
    // section大小
    uint64_t    size;       /* size in bytes of this section */
    // 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 */
    // 标志,section的类型和属性
    uint32_t    flags;      /* flags (section type and attributes)*/
    // 保留(用于偏移量或索引)
    uint32_t    reserved1;  /* reserved (for offset or index) */
    // 保留(用于count或sizeof)
    uint32_t    reserved2;  /* reserved (for count or sizeof) */
    // 保留
    uint32_t    reserved3;  /* reserved */
};

总结:大多数的Mach-O文件均包含以下三个段:

  • __TEXT 代码段:只读,包括函数,和只读的字符串
  • __DATA 数据段:读写,包括可读写的全局变量等
  • __LINKEDIT: __LINKEDIT包含了方法和变量的元数据(位置,偏移量),以及代码签名等信息。