mach-O文件格式

每个Mach-O文件由 Mach-O header,header 后跟 一系列的load commonds,然后接着1个或多个segement,且每一个segement由0到255个section组成。Mach-O使用REL 重定位格式来处理对符号的引用。在查找符号时,Mach-O使用两层名称空间,将每个符号编码成一个“对象/符号名称”对,然后通过对象和符号名称进行线性搜索。

Machg-O文件结构画图表示如下

mach-O文件格式_第1张图片
Mach-O file structure.png

首先是header,在dylib连接器源码中对Mach-O文件header结构描述如下:

/*
 * The 32-bit mach header appears at the very beginning of the object file for
 * 32-bit architectures.
 */
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 */
};

/* Constant for the magic field of the mach_header (32-bit architectures) */
#define MH_MAGIC    0xfeedface  /* the mach magic number */
#define MH_CIGAM    0xcefaedfe  /* NXSwapInt(MH_MAGIC) */

/*
 * The 64-bit mach header appears at the very beginning of object files for
 * 64-bit architectures.
 */
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 */
    uint32_t    flags;      /* flags */
    uint32_t    reserved;   /* reserved */
};

/* Constant for the magic field of the mach_header_64 (64-bit architectures) */
#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */

可以看到64位机器与32位机器头部基本一致,每种文件都有各自的magicNumber,用于标志文件类型。例如,.exe 文件的Magic number 是4D5A, .png 的文件的Magic number 是89 50 4E 47 0D 0A 1A 0A,pdf 的Magic number 是25 50 44 46等等。

cputype 表示cpu类型,包括X86,I386,X86_64,arm64d等
cpusubtype,机器子类型,有VAX780,VAX785,VAX750等
filetype 文件类型,文件部分类型定义如下:

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

使用otool 工具查看xxxframeWork 的header内容

Mach header
    magic           0xfeedface
    cputype         12
    cpusubtype      9
    caps            0x00
    filetype        6
    ncmds           35
    sizeofcmds      4124 
    flags           0x00100085

Mach header

    magic           0xfeedfacf
    cputype         16777228
    cpusubtype      0  
    caps            0x00
    filetype        6
    ncmds           35
    sizeofcmds      4672  
    flags           0x00100085

上面一个是armv7的,下面是arm64的。

同时header中还记录了load command 的个数,所有load Command的大小。load command 结构记录如下

struct load_command {
    uint32_t cmd;       /* type of load command */
    uint32_t cmdsize;   /* total size of command in bytes */
};

load command 直接跟随mach_header。总大小由mach_header中的sizeofcmds字段给出。所有load命令必须具有cmd和cmdsize的前两个字段。 cmd字段用 记录 load命令类型的常量填充。每个load命令类型都有它特殊的结构。这里给出部分load command 类型

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

cmdsize字段是以字节为单位的, 特定的load命令结构,加上它后面所跟随的内容 组成完整load命令。

在terminal下使用otool 工具查看 一下某个framework的 load commands
otool -f xxxFoundationModule | open -f
得到结果如下:

Fat headers
fat_magic 0xcafebabe
nfat_arch 2
architecture 0
    cputype 12
    cpusubtype 9
    capabilities 0x0
    offset 16384
    size 1022336
    align 2^14 (16384)
architecture 1
    cputype 16777228
    cpusubtype 0
    capabilities 0x0
    offset 1048576
    size 1150640
    align 2^14 (16384)

Segment区段在dylib 源码中记录如下(32机器位举例):

struct segment_command { /* for 32-bit architectures */
    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 */
};

其中nsects 字段记录了segment中包含的section个数。
源码中提到,一个segment由0或者多个section组成,MH_OBJECT格式下segment的所有section都在一个段中紧凑排列。没有填充指定的段边界和 mach_header和load命令不是segment的一部分。 部分名称相同名称的section,用sectname表示,进入相同的segement,用segname表示,由链接编辑器组合。

segment 类型主要有__TEXT segment,__DATA segment,__LINKED segment
__TEXT segment 包含了被执行的代码。它被以只读和可执行的方式映射。进程被允许执行这些代码,但是不能修改。这些代码也不能对自己做出修改,因此这些被映射的页从来不会被改变。

__DATA segment 以可读写和不可执行的方式映射。它包含了将会被更改的数据。
__LINKED segment 用于链接器链接的区段。

在 segment中,一般都会有多个 section。它们包含了可执行文件的不同部分。在 __TEXT segment 中,
__text section 包含了编译所得到的机器码。__stubs 和 __stub_helper 是给动态链接器 (dyld) 使用的。
通过这两个 section,在动态链接代码中,可以允许延迟链接。__const (在我们的代码中没有) 是常量,不可变的,
就像 __cstring (包含了可执行文件中的字符串常量 -- 在源码中被双引号包含的字符串) 常量一样。

__DATA segment 中包含了可读写数据。在我们的程序中只有 __nl_symbol_ptr 和 __la_symbol_ptr,
它们分别是 non-lazy 和 lazy 符号指针。延迟符号指针用于可执行文件中调用未定义的函数,例如不包含在可执行文件中的函数,
它们将会延迟加载。而针对非延迟符号指针,当可执行文件被加载同时,也会被加载。

在 _DATA segment 中的其它常见 section 包括 __const,在这里面会包含一些需要重定向的常量数据。
例如 char * const p = "foo"; -- p 指针指向的数据是可变的。__bss section 没有被初始化的静态变量,
例如 static int a; -- ANSI C 标准规定静态变量必须设置为 0。并且在运行时静态变量的值是可以修改的。
__common section 包含未初始化的外部全局变量,跟 static 变量类似。例如在函数外面定义的 int a;。
最后,__dyld 是一个 section 占位符,被用于动态链接器。

引自:Mach-O文件结构

Section 的结构记录如下:

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) */
};

同样可以使用otool查看 segement 区段
otool -o xxxFoundationModule | open -f
-o 代码表输出 OC的 segment

mach-O文件格式_第2张图片
截屏1

mach-O文件格式_第3张图片
截屏2

从输出来看,-o 选项可以查看frameWork中的所有类变异后的虚拟地址,继承关系,公有,私有api的名字,值的返回类型。而且很清晰的表现了OC类在内存里的存储方式。
看到这里感觉新世界的大门打开了,知道了类名,方法列表,如果往ipa的包中加入自己的frameWork,然后对包再重签名,可怕的事情就会发生了。。。。

你可能感兴趣的:(mach-O文件格式)