美文网首页iOS开发
mach-O文件格式

mach-O文件格式

作者: 10m每秒滑行 | 来源:发表于2018-01-31 17:31 被阅读0次

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

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

    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

    截屏1 截屏2

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

    相关文章

      网友评论

        本文标题:mach-O文件格式

        本文链接:https://www.haomeiwen.com/subject/gyslzxtx.html