MachO文件

1、MachO文件的概念

Mach-O 其实是Mach Object文件格式的缩写,是 mac 以及 iOS 上可执行文件的格式, 类似于 windows 上的 PE 格式 (Portable Executable), linux 上的 elf 格式 (Executable and Linking Format) 。常⻅的 .o.a .dylib Frameworkdyld .dsym

2、整体结构如下图

MachO结构图.png

2.1、Header(头部)

Header表明该文件是 Mach-O 格式,指定目标架构,还有一些其他的文件属性信 息,文件头信息影响后续的文件结构安排。
与 Mach-O 文件格式有关的结构体定义都可以从xnu源码loader.h中找到。

32位和64位的mach_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 */
};
  • magic 魔数(特征数字),用来标记当前设备是大端序还是小端序。
  • cputype 标识 CPU 的架构,例如 ARM、ARM64、X86_64 等。
  • cpusubtype 标识 CPU 的具体类型,区分不同版本的处理器。
  • filetype 由于 Mach-O 支持多种类型文件,所以此处引入了 filetype 字段来标明( .o.a .dylib Frameworkdyld .dsym等)
  • ncmds Mach-O 文件中加载命令(load commands)的条数。
  • sizeofcmds Mach-O文件中加载命令(load commands)的总大小。
  • flags 标识着 Mach-O 文件的一些重要信息(可以 loader.h中查看结构,其中MH_PIE启用ASLR)
  • reserved 64位预留字段

2.2、Load commands

Load commands 是一张包含很多内容的表。内容包括区域的位置、符号表、动态符号表 等。用于告诉loader如何设置并加载二进制数据,对系统内核加载器和动态链接器(dyld)起指导作用。
load_command的数据结构

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

前 4 个字节表示类型,不同类型的 load command 作用不一样,紧跟其后的 4 个字节表示该 load command 的大小.
可以通过MachOView软件来查看类容(类型)

64位系统load command内容.png

  • LC_SEGMENT_64 将文件中(32位或64位)的段映射到进程地 址空间中
  • LC_DYLD_INFO_ONLY 动态链接相关信息
  • LC_SYMTAB 符号地址
  • LC_DYSYMTAB 动态符号表地址
  • LC_LOAD_DYLINKER dyld加载
  • LC_UUID 文件的UUID
  • LC_VERSION_MIN_MACOSX 支持最低的操作系统版本
  • LC_SOURCE_VERSION 源代码版本
  • LC_MAIN 设置程序主线程的入口地址和栈大小
  • LC_LOAD_DYLIB 依赖库的路径,包含三方库
  • LC_FUNCTION_STARTS 函数起始地址表
  • LC_CODE_SIGNATURE 代码签名
  • LC_ENCRYPTION_INFOLC_ENCRYPTION_INFO_64:加密信息,如果是从App Store上下载的应用,外面被加了一层壳,对应的加密标记(Crypt ID)不为0,如果不是App Store上下载的应用(例如PP助手上),或这个已经被脱过壳的,加密标记(Crypt ID)便为0

LC_SEGMENT(段)

LC_SEGMENT_64LC_SEGMENT(32位系统)是加载的主要命令,翻译成中文叫做“段”,它负责指导内核来设置进程的内存空间,说白了,只要是这个类型的 load command,系统会将其指示的内容全部加载到指定的虚拟内存地址上来。
MachOView看到的内容

LC_SEGMENT_64.png

通过mach-o/loader.h 找到数据结构

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

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 */
};
  • cmd 该加载命令类型,为LC_SEGMENT_64(64位)或LC_SEGMENT(32位)
  • cmdsize 该加载命令大小,包括segement下session结构所占大小
  • segname[16] segment 名字
  • vmaddr 为当前segment分配的虚拟内存地址
  • vmsize 为当前segment分配的虚拟内存大小
  • fileoff 当前segment在 Mach-O 文件中的偏移量
  • filesize 当前segment在 Mach-O 文件中占用的字节
  • maxprot segment所在页所需要的最高内存保护
  • initprot segment所在页原始内存保护
  • nsects segment中section数量
  • flags 标识符

大致可以这么理解:系统 Mach-O 从fileoff处加载filesie大小的内容到虚拟内存vmaddr处,大小为vmsizesegment页权限initport进行初始化,这些权限可以被修改,但是不能超过maxprot的值。通俗来说就是,fileofffilesie指导和说明了内容从哪里来,vmaddrvmsize指导和说明了文件到哪里去。需要留意,对某些segment来说,vmsize可能会大于 filesize,如__DATA、__LINKEDIT。

segname分类(用下划线和大写字母组成)

  • __PAGEZERO:静态链接器创建了__PAGEZERO命名的段作为可执行文件的第一个段,该段在文件中所占大小为0,在 32 位系统上,加载到虚拟内存中是 0x4000,也就是 16kb,在 64 位系统上,加载到虚拟未存中是 0x100000000,也就是 4GB。是一个不可读、不可写、不可执行的空间,能够在空指针访问时抛出异常。
  • __TEXT:代码段,里面包含了可执行代码和其他一些只读数据,该段是可读、可执行,但是不可写。
  • __DATA:数据段,里面主要是存放将会被更改的数据,该段是可读、可写,但不可执行。
  • __LINKEDIT:包含需要被动态链接器使用的符号和其他表,包括符号表、字符串表等,可读,但不可写不可执行。

segname下的section
segname__TEXT__DATA两个segment可以进一步分解为section。之所以按照segment -> section的结构组织方式,是因为在同一个segment下的section,可以控制相同的权限,也可以不完全按照Page的大小进行内存对其,节省内存的空间。而segment对外整体暴露,在程序载入阶段映射成一个完整的虚拟内存,更好的做到内存对齐。

mach-o/loader.h文件中可以找到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) */
};

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:section名字
  • segname:section所在的segment名称
  • addr:section所在的内存地址
  • size:section的大小
  • offset:section所在的文件偏移
  • align:section的内存对齐边界 (2 的次幂)
  • reloff:重定位信息的文件偏移
  • nreloc:重定位条目的数目
  • flags:标志属性
  • reserved:保留字段

使用 MachOView 进行查看OC工程的macho文件


oc的segname下的section.png

各个section作用如下:

  • __TEXT.__text:主程序代码
  • __TEXT.__stubs__TEXT.__stub_helper:用于帮助动态链接器绑定符号
  • __TEXT.__const:const关键字修饰的常量
  • __TEXT.__objc_methodname:OC方法名
  • __TEXT.__cstring:只读的C语言字符串
  • __TEXT.__objc_classname:OC类名
  • __TEXT.__objc_methtype:OC方法类型(方法签名)
  • __TEXT.__gcc_except_tab__ustring__unwind_info:GCC编译器自动生成,用于确定异常发生是栈所对应的信息(包括栈指针、返回地址及寄存器信息等)
  • __DATA.__got:全局非懒绑定符号指针表
  • __DATA.__la_symbol_ptr:懒绑定符号指针表
  • __DATA.__mod_init_func:C++类的构造函数
  • __DATA.__const:未初始化过的常量
  • __DATA.__cfstring:Core Foundation字符串
  • __DATA.__objc_classlist:OC类列表
  • __DATA.__objc_nlclslist:实现+load方法的 OC 类列表
  • __DATA.__catlist:OC 分类(Category)列表
  • __DATA.__protolist:OC 协议(Protocol)列表
  • __DATA.__imageinfo:镜像信息,可用它区别 OC 1.0与2.0
  • __DATA.__const:OC 初始化过的常量
  • __DATA.__selrefs:OC 选择器(SEL)引用列表
  • __DATA.__protorefs:OC 协议引用列表
  • __DATA.__classrefs:OC 类引用列表
  • __DATA.__superrefs:OC 超类(即父类)引用列表
  • __DATA.__ivar:OC 类的实例变量
  • __DATA.__objc_data:OC 初始化过的变量
  • __DATA.__data:实际初始化数据段
  • __DATA.__common:未初始化过的符号申明
  • __DATA.__bss:未初始化的全局变量

使用 MachOView 进行查看Swift工程的macho文件

swift5的macho.png

在Swift的macho文件中,types是4字节存储信息,存储的是相对地址
注意:swift5的macho会多出几个section(目前知道的就是下面几个,后面了解再补充)

__swift5_types:存储的是Class、Struct、Enum的描述(Descriptor)偏移信息
__swift5_fieldmd:存储的是属性描述(属性信息)的偏移信息
__swift5_reflstr:存储的是属性名称
__swift5_protos:存储的是代理的描述

2.3、Data

Data 区主要就是负责代码和数据记录的。Mach-O 是以 Segment 这种结构来组织数据 的,一个 Segment 可以包含 0 个或多个 Section。根据 Segment 是映射的哪一个 Load Command,Segment 中 section 就可以被解读为是是代码,常量或者一些其他的数据类 型。在装载在内存中时,也是根据 Segment 做内存映射的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • MachO文件 Mach-O是Mach Object文件格式的缩写,是mac以及iOS上可执行文件的格式。是一种用...
    忻凯同学阅读 5,542评论 0 1
  • 前言 本篇文章主要分析MachO文件(也称作二进制可执行文件),相信大家在平时开发中都会碰到MachO文件这个概念...
    深圳_你要的昵称阅读 4,179评论 0 2
  • 在上一篇文章中,咱们已经简单的提到了MachO,在用Framework做代码注入的时候,必须先向MachO的Loa...
    蚂蚁也疯狂阅读 6,001评论 1 4
  • MachO文件 Mach-O其实就是Mach Object文件格式的缩写,是mac以及iOS上可执行文件的格式,类...
    coder_feng阅读 4,003评论 0 1
  • MachO文件 前言 Mach-O(Mach Object):Mach-O 文件是Mach object文件格式的...
    superFool阅读 4,413评论 0 0

友情链接更多精彩内容