Mach-O文件

Mach-O文件

UNIX标准制定了一个通用的可移植的二进制格式文件,叫ELF,然而OSX却维护了一个自己独有的二进制格式:Mach-Object(Mach-O)

Mach-O没有类似于 XML,YAML,JSON等诸如此类的特殊格式,它只是一个二进制字节流,被划分为了有意义的数据块。这些块包含元信息,比如,字节顺序,CPU类型,块的大小,等等。

熟悉Mach-O文件格式,有助于了解苹果底层软件运行机制,更好的掌握dyld(dyld是苹果的动态链接器,是苹果操作系统一个重要组成部分,在系统内核做好程序准备工作之后,交由dyld负责余下的工作)加载Mach-O的步骤。

Mach-O文件类型

对于OSX和iOS来说,Mach-O是其可执行文件的格式,主要包括以下几种文件类型:

  • Executable: 应用的主要二进制
  • Dylib: 动态链接库
  • Bundle: 不能被链接,只能在运行时使用dlopen加载
  • Image: 包含Executable、Dylib和Bundle
  • Framework: 包含Dylib、资源文件和头文件的文件夹

我们可以看下面这张 Mach-O 镜像文件格式:


Mach-O文件_第1张图片
640.jpg

通过上图,可以看出Mach-O主要由以下三部分组成:

  1. Mach-O 头部(Mach Header)
    主要是描述了Mach-O的cpu架构,文件类型,以及加载命令等信息。它能帮助校验Mach-O合法性和定位文件的运行环境。
  2. 加载命令 (load command)
    描述了文件中数据的具体组织结构,不同的数据类型使用不同的加载命令表示,其占用的内存和加载命令的总数在Headers中已经指出。
  3. Data
    Data中每个段(segment)的具体数据都保存在这里,每个段都有一个或多个Section,它们存放了具体的数据和代码,主要包含代码,数据,例如符号表,动态符号表等等。
Mach-O头部

与Mach-O文件格式有关的结构体,针对32位和64位架构的cpu,分别使用了mach_header和mach_header_64结构体来描述Mach-O头部。
mach_header结构体的定义如下:

struct mach_header {
    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 */
    uint32_t    flags;        /* flags */
};
  • magic: 标志符,用来标识Mach-O的平台属性,确认文件的类型。操作系统在加载时会确定标志符是否正确,不正确会拒绝加载
  • cputype / cpusubtype: cpu的类型
  • filetype: 标识Mach-O文件的具体类型,比如 MH_EXECUTE,代表可执行文件,MH_DYLINKER,表明该文件是动态链接器程序文件,符号文件(DYSM)。
  • ncmds :指明了Mach-O文件中加载命令 load commands 的个数
  • flags :标记该文件目前的状态信息,比如MH_NOUNDEFS:表明该文件里没有未定义的符号引用,MH_DYLDLINK: 表明该文件,已经完成了静态链接,不能再次被静态链接。

我们可以使用 MachView 查看Mach-O文件的结构,可以对比上述所说分析一下这张图:


Mach-O文件_第2张图片
屏幕快照 2019-09-05 上午10.37.47.png
Load Commands

在mach_header之后是Load Command加载命令,这些加载命令在Mach-O文件加载解析时,被内核加载器或者动态链接器调用,在内存中的结构如下:

struct load_command {
    uint32_t cmd;        /* type of load command */
    uint32_t cmdsize;    /* total size of command in bytes */
};
  1. cmd: 具体的加载类型,比如
  • LC_SEGMENT: 表示这是一个段加载命令,需要将它加载到对应的进程空间上去。
  • LC_SYMSEG: 符号表信息,符号表中详细说明了代码中所用符号的信息等。
  • LC_LOAD_DYLIB: 表示这是一个需要动态加载的链接库,它使用dylib_command结构体表示。
  • LC_MAIN: 此加载命令记录了可执行文件的主函数main()的位置,它使用entry_point_command结构体表示。
  1. cmdsize: 具体的load_command结构所占内存的大小。
Segment & Section

Mach-O可执行文件,把一个或者多个属性相似的 section 合并成一个segment,在装载的时候就可以把他们看成整体进行映射(分页),这样可以减少页内碎片。操作系统是按segment进行映射的。另外,segment的对其属性取决于section中最大的对其属性值。

    uint32_t    cmd;        /* LC_SEGMENT */
    uint32_t    cmdsize;    /* includes sizeof section structs */
    char        segname[16];    /* segment name */
    uint32_t    vmaddr;     /* memory address of this segment */
    uint32_t    vmsize;     /* memory size of this segment */
    uint32_t    fileoff;    /* file offset of this segment */
    uint32_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 */
};
  • segname segment的名称
  • vmaddr 段的虚拟内存起始地址
  • vmsize 段的虚拟内存大小
  • fileoff 段在文件中的偏移量
  • filesize 段在文件中的大小
  • maxprot 段页面所需要的最高内存保护(可读 可写 可执行)
  • initprot 段页面初始的内存保护
  • nsects 段中包含section的数量
  • flags 其他杂项标志位。

可以结合下面这张图去理解:


Mach-O文件_第3张图片
屏幕快照 2019-09-05 上午11.14.37.png
Section数据

正如上所说,当一个段包含多个节区时,节区信息会以数组的形式紧随着存储在段加载命令后面,节区使用结构体section表示(64位使用section_64表示),定义如下:

struct section { /* for 32-bit architectures */
    char        sectname[16];    /* name of this section */
    char        segname[16];    /* segment this section goes in */
    uint32_t    addr;        /* memory address of this section */
    uint32_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) */
};
  • sectname 就是这个section的名称
  • segname 该section所属的segment名
  • addr 该section在内存的起始位置
  • size 该section的大小
  • offset 该section的文件偏移
  • align 字节大小对齐
  • reloff 重定位入口的文件偏移
  • nreloc 需要重定位的入口数量
  • flags包含section的type和attributes

可以对比下图去理解一下:


Mach-O文件_第4张图片
屏幕快照 2019-09-05 上午11.26.01.png

我们的项目中难免会存在一些没使用的类或方法,由于 OC 的动态特性,编译器会对所有的源文件进行编译,找出并删除没用到的类或方法可以减少可执行文件大小。
__objc_classlist 和 __objc_classrefs,它们分别表示项目中全部类列表和项目中被引用的类列表,那么取两者之差,就能删除一些项目中没使用的类文件。

通用二进制(胖二进制)

通用二进制格式由多种架构的Mach-O文件合并而成,通过Fat Header来记录不同架构在文件中的偏移量。

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