mach-O文件结构

896 阅读9分钟

概念

Mach-O,是Mach object文件格式缩写,是一种用于记录可执行文件、对象代码、共享库、动态加载代码和内存转储的文件格式,是macOS/iOS上程序以及库的标准格式。

作为 a.out 格式的替代品,Mach-O 提供了更好的扩展性,并提升了符号表中信息的访问速度。

mach-o文件类型分为:

  1. Executable:应用的主要二进制
  2. Dylib Library:动态链接库(DSO或DLL)
  3. Static Library:静态链接库
  4. Bundle:不能被链接的Dylib,只能在运行时使用dlopen()加载,可当作MacOS插件
  5. Relocatable Object File:可重定向文件类型

FatFile/FatBinary:是一个由不同的编译架构的Mach-O产物所合成的集合体。==一个架构的mach-O只能在相同架构的机器或者模拟器上用,为了支持不同架构需要一个集合体==。

非FAT格式(一般Mach-O格式)

Mach-O执行步骤

可执行文件执行的主要步骤

  1. 操作系统读取可执行文件头部检验可执行文件的有效性,比如模数等
  2. 通过LCL_LOAD_DYLINKER结构,设置动态链接器的路径
  3. 根据 segment 结构开始对 Mach-o 进行映射
  4. 初始化 Mach-o 进程环境
  5. 将系统调用的返回地址设置为动态链接器的入口地址
  6. 加载LC_LOAD_DYLIB中的动态库,进行转载时重定位
  7. 跳转到程序的入口地址开始执行

Mach-O文件

相关源码:opensource.apple.com/tarballs/xn… 文件:EXTERNAL_HEADERS/mach-o/fat.h loader.h

#define MH_OBJECT   0x1     /* 目标文件*/
#define MH_EXECUTE  0x2     /* 可执行文件 */
#define MH_FVMLIB   0x3     /* fixed VM shared library file */
#define MH_CORE     0x4     /*核心转储文件 */
#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 */

Mach-O包含三个主要区域

  • Header:文件类型,目标架构
  • Load command:描述文件在虚拟内存中的逻辑和布局
  • Raw segment data:原始数据

利用MachOView查看可执行文件

  • 文件头 mach64 Header
  • 加载命令 Load Commands
  • 文本段 __TEXT
  • 数据段 __DATA
  • 动态库加载信息 Dynamic Loader Info
  • 入口函数 Function Starts
  • 符号表 Symbol Table
  • 动态库符号表 Dynamic Symbol Table
  • 字符串表 String Table

Mach64 Header

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

Load Commands

struct load_command {
    uint32_t cmd;        /* type of load command */
    uint32_t cmdsize;    /* total size of command in bytes */
};
  • cmd:具体的加载类型,比如: LC_SEGMENT:表明下面加载 segment 部分,该部分是可以被加载到内存中,被页表项映射的内容。 LC_SYMSEG:符号表信息,符号表中详细说明了代码中所用符号的信息等
  • cmdsize:具体的load_command结构所占内存的大小。

The segment load command

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

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 */
};
  • vmaddr:映射到的虚拟地址,确定该 segment 的启始的也目录项和页表项。
  • vmsize:最大想虚拟内存大小
  • fileoff:该 segment 的内容在文件内的偏移位置
  • filesize:该 segment 的大小
  • maxprot:所有可选的保护属性(可读、可写、可执行)
  • initprot:至少应该有的保护属性
  • flag:虚拟内存相关的标识

section

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 */
};
  • addr:虚拟内存地址
  • offset:该内容在文件内的偏移
  • align:对其属性,为2^align。当操作系统处理缺页相关的中断时,可以方便的查找出所缺内容,在文件的偏移。
  • reloc和 nreloc目前都是0,相应的信息放在了struct dysymtab_command中
  • reserved1:如果该 section 含有symbol pointers和routine stubs,这里存放Dynamic Symbols Table中相应的下标
  • reserved2:对于S_SYMBOL_STUBS类型的 section,这里存放桩(stub)大小。

桩(stub):由于要实现动态链接延迟加载,因此需要一个结构,在引用延迟加载的符号的时候,调用动态链接器中相应的接口,去找到该符号的地址。非延迟加载的时候,直接会去取数据段里动态链接符号的地址。通过这种方式,程序模块中共享模块中的指令,不会随着共享模块装载的地址改变而改变。做到了共享模块指令部分在多个进程间的共享,做到了真正的地址无关。

模块(moudle):在静态链接下,整个程序最终只有一个可执行文件。是一个不可分割的部分。而在动态链接下,一个程序被分成若干个文件,有可执行文件部分,和程序所依赖的共享对象,把这些部分叫做模块。

LC_DYLD_INFO

struct dyld_info_command {
   uint32_t   cmd;      /* LC_DYLD_INFO or LC_DYLD_INFO_ONLY */
   uint32_t   cmdsize;      /* sizeof(struct dyld_info_command) 
    uint32_t   rebase_off;  /* file offset to rebase info  */
    uint32_t   rebase_size; /* size of rebase info   */
    uint32_t   bind_off;    /* file offset to binding info   */
    uint32_t   bind_size;   /* size of binding info  */
    uint32_t   weak_bind_off;   /* file offset to weak binding info   */
    uint32_t   weak_bind_size;  /* size of weak binding info  */

    uint32_t   export_off;  /* file offset to lazy binding info */
    uint32_t   export_size; /* size of lazy binding infs */
};
  • rebase_off:基址重定位表在文件的偏移。其为了解决共享模块绝对地址引用问题。比如共享模块有如下代码: static int a; static int *p = &a; 因为共享模块的数据部分,会在可执行文件里有个副本,加入 a 在可执行文件模块的基址为 A,那么 p 的值也必须加 A。 表中数据项(seg-index, seg-offset, type)
  • bind_off:动态绑定信息表偏移。这个表里的符号要求,在动态绑定阶段,动态链接器,确切的找到符号的地址。表中数据项(seg-index, seg-offset, type, symbol-library-ordinal, symbol-name, addend)
  • weak_bind_off:弱绑定表,该表中的符号,如果和动态库中的符号冲突时,用弱绑定表的符号。
  • lazy_bind_off:延迟加载表。延迟加载,对于一些符号,我们不必在装载时进行重定位,这样会加快程序的启动速度.
  • export_off:暴露给外界的符号表偏移。

LC_SYMTAB

struct symtab_command {
    uint32_t    cmd;        /* LC_SYMTAB */
    uint32_t    cmdsize;    /* sizeof(struct symtab_command) */
    uint32_t    symoff;     /* symbol table offset */
    uint32_t    nsyms;      /* number of symbol table entries */
    uint32_t    stroff;     /* string table offset */
    uint32_t    strsize;    /* string table size in bytes */
};
  • symoff:符号表偏移
  • stroff:字符串表偏移,记录了所有符号的名字

LC_DYSYMTAB

struct dysymtab_command {
    uint32_t cmd;   /* LC_DYSYMTAB */
    uint32_t cmdsize;   /* sizeof(struct dysymtab_command) */
    uint32_t ilocalsym; /* index to local symbols */
    uint32_t nlocalsym; /* number of local symbols */
    uint32_t iextdefsym;/* index to externally defined symbols *  
    int32_t nextdefsym;/* number of externally defined symbols *
    uint32_t iundefsym; /* index to undefined symbols */
    uint32_t nundefsym; /* number of undefined symbols */
    uint32_t tocoff;    /* file offset to table of contents */
    uint32_t ntoc;  /* number of entries in table of contents */
    uint32_t modtaboff; /* file offset to module table */
    uint32_t nmodtab;   /* number of module table entries */
    uint32_t extrefsymoff;  /* offset to referenced symbol table
    uint32_t nextrefsyms;   /* number of referenced symbol table entries */

    uint32_t indirectsymoff; /* file offset to the indirect symbol table */
    uint32_t nindirectsyms;  /* number of indirect symbol table entries */
    uint32_t extreloff; /* offset to external relocation entries 
    uint32_t nextrel;   /* number of external relocation entries
    uint32_t locreloff; /* offset to local relocation entries */
    uint32_t nlocrel;   /* number of local relocation entries */
};
  • ilocalsym、iextdefsym、iundefsym把符号表分为三个区域,ilocalsym 本地符号仅用于调试,iextdefsym可执行文件定义的符号,iundefsym可执行文件里没有定义的
  • tocoff,目录偏移,该内容只有在动态分享库中存在。主要作用是把符号和定义它的模块对应起来。
  • modtaboff:为了支持“模块”(整个对象文件)的动态绑定,符号表必须知道文件创建的模块。该内容只有在动态分享库中存在。
  • extrefsymoff:为了支持动态绑定模块,每个模块都有一个引用符号表,符号表里存放着每个模块所引用的符号(定义的和没有定义的)。该表指针动态库中存在。
  • indirectsymoff:如果 section 中有符号指针或者桩(stub),section中的reserved1存放该表的下标。间接符号表,只是存放一些32位小标,这些下标执行符号表。
  • extreloff:每个模块都有一个重定义外部符号表。仅在动态库中存在
  • locreloff:重定义本地符号表,由于只是在调试中用,所以不必更加模块分组。

LC_LOAD_DYLIB

struct dylib {
    union lc_str  name;         /* library's path name */
    uint32_t timestamp;         /* library's build time stamp */
    uint32_t current_version;       /* library's current version number */
    uint32_t compatibility_version; /* library's compatibility vers number*/
};
struct dylib_command {
    uint32_t    cmd;        /* LC_ID_DYLIB, LC_LOAD_{,WEAK_}DYLIB,
                       LC_REEXPORT_DYLIB */
    uint32_t    cmdsize;    /* includes pathname string */
    struct dylib    dylib;      /* the library identification */
};

dylib:说明了动态链接器的地址,创建的时间,版本号

LC_UUID

struct uuid_command {
    uint32_t    cmd;        /* LC_UUID */
    uint32_t    cmdsize;    /* sizeof(struct uuid_command) */
    uint8_t uuid[16];   /* the 128-bit uuid */
};

LC_VERSION_MIN_MACOSX

struct entry_point_command {
    uint32_t  cmd;  /* LC_MAIN only used in MH_EXECUTE filetypes */
    uint32_t  cmdsize;  /* 24 */
    uint64_t  entryoff; /* file (__TEXT) offset of main() */
    uint64_t  stacksize;/* if not zero, initial stack size */
};
  • entryoff:main 函数的位置
  • stacksize初始化栈的大小,通过-stack_size设置,默认为0

LC_LOAD_DYLIB

struct dylib_command {
    uint32_t    cmd;        /* LC_ID_DYLIB, LC_LOAD_{,WEAK_}DYLIB,
                       LC_REEXPORT_DYLIB */
    uint32_t    cmdsize;    /* includes pathname string */
    struct dylib    dylib;      /* the library identification */
};

dylib:指出需要加载的动态库的路径,版本号

LC_FUNCTION_STARTS

struct linkedit_data_command {
    uint32_t    cmd;        /* LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO,
                                   LC_FUNCTION_STARTS, LC_DATA_IN_CODE,
                   LC_DYLIB_CODE_SIGN_DRS or
                   LC_LINKER_OPTIMIZATION_HINT. */
    uint32_t    cmdsize;    /* sizeof(struct linkedit_data_command) */
    uint32_t    dataoff;    /* file offset of data in __LINKEDIT segment */
    uint32_t    datasize;   /* file size of data in __LINKEDIT segment  */
};

dataoff:记录入口函数地址数据在文件的偏移

符号表

struct nlist_64 {
    union {
        uint32_t  n_strx; /* index into the string table */
    } n_un;
    uint8_t n_type;        /* type flag, see below */
    uint8_t n_sect;        /* section number or NO_SECT */
    uint16_t n_desc;       /* see <mach-o/stab.h> */
    uint64_t n_value;      /* value of this symbol (or stab offset) */
};
  • n_un:在字符串标中的下标
  • n_type:分为三个部分 #define N_EXT 0x01 代表该符号是否对外部可见 #define N_TYPE 0x0e 0x0代表未定义,0x2代表了绝对引用,(静态库里的符号),0xe,该符号已经被定义了,此时 n_value 为 section 的下标。0xc,该符号被定义在动态库里。0xa,间接的符号引用
  • n_desc:标识该符号是否延迟加载,是否为弱引用等
  • n_value:符号的装载地址

字符串表StringTable

其就是一个字符数组,用来存放所有变量的名字。