美文网首页人民广场
了解 Mach-O 文件

了解 Mach-O 文件

作者: _涼城 | 来源:发表于2022-03-21 14:28 被阅读0次

    什么是 Mach-O

        Mach-O 其实是 Mach Object 文件格式的缩写,它是一种用于可执行文件、目标代码、动态库的文件格式,作为 a.out 格式的替代, Mach-O 提供了更强的扩展性。在 OS X 中,内核扩展、命令行工具、应用程序、框架和库(共享和静态)是使用 Mach-O 文件实现的。可以在 Mach-O Programming Topics 查看相关介绍。

    分析 Mach-O 文件的工具

    • /usr/bin/lipo
      查看二进制文件信息,可以生成或者拆分多架构文件
    • /usr/bin/file
      显示文件的类型。对于多架构文件,它会显示构成存档的每个图像的类型。
    • /usr/bin/otool
      列出了 Mach-O 文件中特定部分和段的内容。它包括每个支持的体系结构的符号反汇编器,并且它知道如何格式化许多常见节类型的内容。
    • /usr/bin/pagestuff
      显示关于组成图像的每个逻辑页面的信息,包括每个页面中包含的部分和符号的名称。此工具不适用于包含多个架构的图像的二进制文件。
    • /usr/bin/nm
      允许查看目标文件符号表的内容。
    • MachoView.app
      MachoView.app
      在查看 Mach-O 文件之前,有必要了解MachOView.app 工具,MachOView工具属于免费开源项目,源代码可在Github-MachOView下载,可以在 Mac 中查看 Mach-O文件的详细信息

    Mach-O 文件结构

    一个 Mach-O 文件包含三个主要区域(如下所示):

    • Header:指定文件的目标架构
    • Load commands :指定文件的逻辑结构和文件在虚拟内存中的布局。
    • Raw segment data:包含加载命令中定义的段的原始数据。
    Apple Mach-O 文件结构

    当然,可以通过 MachOView 对 App 可执行文件进行查看,Mach-O 文件里面的内容如下图所示:

    Mach-O 文件结构

    Mach64 Header

    每个 Mach-O 文件的开头都有一个 Header ,用于将文件标识为 Mach-O 文件。Header 包含该二进制文件的一般信息。字节顺序、架构类型、加载指令的数量等。使得可以快速确认一些信息,比如当前文件用于32位还是64位,对应的处理器是什么、文件类型是什么

    Mach64 Header 结构

    macho/loader.h 中,可以查看到 Mach-O header 信息结构代码如下:

    
    struct mach_header_64 {
        uint32_t        magic;      // 64位还是32位
        cpu_type_t      cputype;  
        cpu_subtype_t   cpusubtype; // CPU 子类型,比如 armv8 CPU_SUBTYPE_ARM_64
        uint32_t        filetype;   // 文件类型 MH_EXECUTE
        uint32_t        ncmds;      // load commands 的数量
        uint32_t        sizeofcmds; // load commands 大小
        uint32_t        flags;      // 标签
        uint32_t        reserved;   // 保留字段
    };
    

        如上面代码所示,包含了

    • magic
      表示是 64 位还是 32 位的、
    • cputype
      CPU 类型,比如 CPU_TYPE_ARM,可以在 macho/machine.h 文件中查看其他。
    • cpusubtype
      CPU 子类型,比如 CPU_SUBTYPE_ARM64_ALL,可以在 macho/machine.h 文件中查看其他。
    • filetype
      文件类型
    • ncmds
      load commands 的数量和大小等文件信息
    • sizeofcmds
      load commands 大小
    • flags
      标志位,标识二进制文件支持的功能。
    • reserved
      保留字段

    Mach-O 文件类型

    其中,文件类型 filetype 表示了当前 Mach-O 属于哪种类型。下面源代码列举了 filetype 的全部类型:

    /*
    * Constants for the filetype field of the mach_header
     */
    #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
            sections */
    #define MH_KEXT_BUNDLE 0xb  /* x86_64 kexts */
    #define MH_FILESET 0xc  /* a file composed of other Mach-Os to
            be run in the same userspace sharing
            a single linkedit. */
    

    其中,部分类型是我们开发中常用的:

    • OBJECT,指的是 .o 文件或者 .a 文件;
    • EXECUTE,指的是 IPA 拆包后的可执行文件;
    • DYLIB,指的是 .dylib 或 .framework 文件;
    • DYLINKER,指的是动态链接器;
    • DSYM,指的是保存有符号信息用于分析闪退信息的文件。

    Load Commands

    紧随 Header 的是一系列的 Load Commands ,负责描述文件在虚拟内存中逻辑结构和布局,这些加载指令清晰地告诉加载器如何处理二进制数据,有些命令是由内核处理的,有些是由动态链接器处理的。除了其他信息,Load Commands 可以指定:

    • 文件在虚拟内存中的初始布局

    • 符号表的位置(用于动态链接)

    • 程序主线程的初始执行状态

    • 包含主可执行文件的导入符号定义的共享库的名称

    Load Commands结构

    macho/loader.h 中,可以查看到 load_command 信息结构代码如下:

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

    Load Commands 类型

    下面列举了 load commands 全部的类型:

    
    /* Constants for the cmd field of all load commands, the type */
    #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) */
    #define LC_DYSYMTAB 0xb /* dynamic link-edit symbol table info */
    #define LC_LOAD_DYLIB 0xc /* load a dynamically linked shared library */
    #define LC_ID_DYLIB 0xd /* dynamically linked shared lib ident */
    #define LC_LOAD_DYLINKER 0xe /* load a dynamic linker */
    #define LC_ID_DYLINKER 0xf /* dynamic linker identification */
    #define LC_PREBOUND_DYLIB 0x10 /* modules prebound for a dynamically */
        /*  linked shared library */
    #define LC_ROUTINES 0x11 /* image routines */
    #define LC_SUB_FRAMEWORK 0x12 /* sub framework */
    #define LC_SUB_UMBRELLA 0x13 /* sub umbrella */
    #define LC_SUB_CLIENT 0x14 /* sub client */
    #define LC_SUB_LIBRARY  0x15 /* sub library */
    #define LC_TWOLEVEL_HINTS 0x16 /* two-level namespace lookup hints */
    #define LC_PREBIND_CKSUM  0x17 /* prebind checksum */
    
    /*
     * load a dynamically linked shared library that is allowed to be missing
     * (all symbols are weak imported).
     */
    #define LC_LOAD_WEAK_DYLIB (0x18 | LC_REQ_DYLD)
    
    #define LC_SEGMENT_64 0x19 /* 64-bit segment of this file to be
           mapped */
    #define LC_ROUTINES_64 0x1a /* 64-bit image routines */
    #define LC_UUID  0x1b /* the uuid */
    #define LC_RPATH       (0x1c | LC_REQ_DYLD)    /* runpath additions */
    #define LC_CODE_SIGNATURE 0x1d /* local of code signature */
    #define LC_SEGMENT_SPLIT_INFO 0x1e /* local of info to split segments */
    #define LC_REEXPORT_DYLIB (0x1f | LC_REQ_DYLD) /* load and re-export dylib */
    #define LC_LAZY_LOAD_DYLIB 0x20 /* delay load of dylib until first use */
    #define LC_ENCRYPTION_INFO 0x21 /* encrypted segment information */
    #define LC_DYLD_INFO  0x22 /* compressed dyld information */
    #define LC_DYLD_INFO_ONLY (0x22|LC_REQ_DYLD) /* compressed dyld information only */
    #define LC_LOAD_UPWARD_DYLIB (0x23 | LC_REQ_DYLD) /* load upward dylib */
    #define LC_VERSION_MIN_MACOSX 0x24   /* build for MacOSX min OS version */
    #define LC_VERSION_MIN_IPHONEOS 0x25 /* build for iPhoneOS min OS version */
    #define LC_FUNCTION_STARTS 0x26 /* compressed table of function start addresses */
    #define LC_DYLD_ENVIRONMENT 0x27 /* string for dyld to treat
            like environment variable */
    #define LC_MAIN (0x28|LC_REQ_DYLD) /* replacement for LC_UNIXTHREAD */
    #define LC_DATA_IN_CODE 0x29 /* table of non-instructions in __text */
    #define LC_SOURCE_VERSION 0x2A /* source version used to build binary */
    #define LC_DYLIB_CODE_SIGN_DRS 0x2B /* Code signing DRs copied from linked dylibs */
    #define LC_ENCRYPTION_INFO_64 0x2C /* 64-bit encrypted segment information */
    #define LC_LINKER_OPTION 0x2D /* linker options in MH_OBJECT files */
    #define LC_LINKER_OPTIMIZATION_HINT 0x2E /* optimization hints in MH_OBJECT files */
    #define LC_VERSION_MIN_TVOS 0x2F /* build for AppleTV min OS version */
    #define LC_VERSION_MIN_WATCHOS 0x30 /* build for Watch min OS version */
    #define LC_NOTE 0x31 /* arbitrary data included within a Mach-O file */
    #define LC_BUILD_VERSION 0x32 /* build for platform min OS version */
    #define LC_DYLD_EXPORTS_TRIE (0x33 | LC_REQ_DYLD) /* used with linkedit_data_command, payload is trie */
    #define LC_DYLD_CHAINED_FIXUPS (0x34 | LC_REQ_DYLD) /* used with linkedit_data_command */
    #define LC_FILESET_ENTRY (0x35 | LC_REQ_DYLD) /* used with fileset_entry_command */
    

    通过 MachOView 来继续查看 LoadCommands 内容

    MachOView 中查看的 LoadCommands
    对应的描述如下表格:
    load commands Type 描述
    LG_SEGMENT_64 将文件中(32位或64位)的段映射到进程地址空间中
    LC_DYLD_INFO_ONLY 动态链接相关信息 rebase、bind
    LC_SYMTAB 符号表
    LC_DYSYMTAB 动态符号表
    LC_UUID 文件的 UUID
    LC_VERSION_MIN_IPHONES 支持最低的操作系统版本
    LC_SOURCE_VERSION 源代码版本
    LC_MAIN 程序入口地址和栈大小
    LC_LOAD_DYLIB 依赖系统库路径
    LC_RPATH 运行时优先搜索依赖库的目录路径
    LC_FUNCTION_STARTS 函数起始地址表
    LC_CODE_SIGNATURE 代码签名

    Segment_Command

    LC_SEGMENT_64LC_SEGMENT 是加载的主要命令,它负责指导内核来设置进程的内存空间,下面是

    struct segment_command_64 { /* for 64-bit architectures */
        uint32_t    cmd;        /* LC_SEGMENT_64 */
        uint32_t    cmdsize;    /* includes sizeof section_64 structs */
        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 */
        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 */
    };
    
    
    • cmdsize
      代表 load command 的大小
    • segname
      段名称
      • __PAGEZERO 作为可执行文件的第一个段。该段位于虚拟内存位置 0 并且没有分配保护权限。该__PAGEZERO段是当前体系结构的一个完整 VM 页面的大小。因为 __PAGEZERO 段中没有数据,所以不占用文件中的空间
      • __TEXT 对应的就是代码段
      • __DATA 对应的是可读/可写的数据
      • __OBJC 包含由 Objective-C 语言运行时支持库使用的数据
      • __LINKEDIT 是支持dyld的,里面包含一些符号表等数据
      • __IMPORT 包含符号存根和指向未在可执行文件中定义的符号的非惰性指针。此段仅为针对 IA-32 架构的可执行文件生成。
    • VM Address
      段的虚拟内存地址
    • VM Size
      段的虚拟内存大小
    • file offset
      段在文件中偏移量
    • file size:段在文件中的大小
      将该段对应的文件内容加载到内存中:从offset处加载 file size大小到虚拟内存 vmaddr处
    • nsects
      标示了Segment中有多少secetion

    通过 MachOView 查看 TEXT 段 内容

    MachOView 中 LG_SEGMENT_64(_TEXT)

    Raw segment data

    Load Commands 之后,所有的 Mach-O 文件都包含一个或多个 Segment 的数据。Segment 的每个部分都包含某种特定类型的代码或数据,每个 Segment 定义了一个虚拟内存区域,动态链接器将其映射到进程的地址空间。Segments 和 Sections 的确切数量和布局由加载命令和文件类型指定

    Section_64 结构

    macho/loader.h 中,可以查看到 section_64 信息结构代码如下:

    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 */
    };
    
    • sectname
      比如_text、stubs
    • segname
      该 section 所属的 segment ,比如__TEXT(主程序),__DATA(数据段)
    • addr
      该 section在内存的起始位置
    • size
      该 section 的大小
    • offset
      该 section 的文件偏移
    • align
      字节大小对齐
    • reloff
      重定位入口的文件偏移
    • nreloc
      需要重定位的入口数量
    • flags
      包含 section 的 type 和 attributes

    Section 的类型

    名称 描述 所属部分
    __TEXT,__text 可执行机器码。编译器通常只在此部分中放置可执行代码,而不放置任何类型的表或数据。 __TEXT
    __TEXT,__cstring 常量 C 字符串。静态链接器在构建最终产品时合并常量 C 字符串值,删除重复项。 __TEXT
    __TEXT,__picsymbol_stub 与位置无关的间接符号存根 __TEXT
    __TEXT,__symbol_stub 间接符号存根。 __TEXT
    __TEXT,__const 初始化常量。 __TEXT
    __TEXT,__literal4 4 字节字面量 __TEXT
    __TEXT,__literal8 8 字节字面量 __TEXT
    __DATA,__data 已初始化的可变变量 __DATA
    __DATA,__la_symbol_ptr 懒加载符号指针 __DATA
    __DATA,__nl_symbol_ptr 非懒加载符号指针 __DATA
    __DATA,__dyld 动态链接器使用的占位符部分 __DATA
    __DATA,__const 初始化的可重定位常量 __DATA
    __DATA,__mod_init_func 静态构造函数 __DATA
    __DATA,__mod_term_func 模块终止功能 __DATA
    __DATA,__bss 未初始化静态变量的数据 __DATA
    __DATA,__common 位于全局范围内的变量声明 __DATA

    相关文章

      网友评论

        本文标题:了解 Mach-O 文件

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