美文网首页
06---MachO

06---MachO

作者: 清风烈酒2157 | 来源:发表于2020-07-26 23:22 被阅读0次

    [toc]

    MachO文件概述

    Mach-O其实是Mach Object文件格式的缩写,是mac以及iOS上可执行文件的格式, 类似于windows上的PE格式 (Portable Executable ), linux上的ELF格式 (Executable and Linking Format).

    可执行文件

    Mach)常见文件格式

    • 目标文件.o
    • 库文件.a .dylib Framework
    • 可执行文件
    • dyld
    • .dsym

    File指令

    • 通过 $file 文件路径 查看文件类型。
      image.png

    MatchO有很多不同的类型,可以通过在Xcode上指定。

    image.png

    通用二进制文件

    1. 苹果公司提出的一种程序代码。能同时适用多种架构的二进制文件
    2. 同一个程序包中同时为多种架构提供最理想的性能。
    3. 因为需要储存多种代码,通用二进制应用程序通常比单一平台二进制的程序要大。
    4. 但是 由于两种架构有共通的非执行资源,所以并不会达到单一版本的两倍之多。
      而且由于执行中只调用一部分代码,运行起来也不需要额外的内存。

    `MachO``文包含的架构资源共享,但是代码部分不共享.

    • Xcode编译可以指定生成哪些架构的Match-O文件(Architectures和Valid Architectures交集)
    image.png
    • lipo -info 查看machO文件架构
    image.png
    • 使用lipo -info 可以查看MachO文件包含的架构

    • lipo MachO文件 –thin 架构–output 输出文件路径使用lipo -create合并多种架构$lipo -create MachO1 MachO2 -output 输出文件路径

    image.png

    Mach文件结构

    image.png

    Header

    作用:快速确认Mach-O文件的基本信息,如运行环境,Load Commands概述。
    数据结构:

    64位架构

    
    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文件运行框架,如64位/32位
    cpu:CPU类型,如arm
    cpusubtype:对应CPU类型的具体型号
    filetype:文件类型
    ncmds:加载命令条数
    sizeofcmds:所有加载命令的大小
    flags:保留字段
    reserved:标志位

    • 使用MachOView工具查看Mach-O文件,如下图:
    image.png

    Mach-O文件运行基本环境:
    cpu:x86,64位
    文件类型:可执行二进制文件
    LoadCommand55LoadCommand,占用大小位1360

    Load commands

    load Commands是由很多的LC_Type组成的,而LC_Type有很多种,可在文件loader.h文件中查看

    作用:
    Load Commands 加载指令,告诉加载器如何处理二进制数据,处理对方分别为内核,动态链接器等。加载指令紧跟在Header后的加载命令区。Load Commands 加载指令个数及大小在Header中定义( commands 的大小总和即为 Header->sizeofcmds 字段,共有 Header->ncmds 条加载命令)。

    数据结构:

    
    struct load_command {
        uint32_t cmd;       /* type of load command */
        uint32_t cmdsize;   /* total size of command in bytes */
    };
    
    
    • cmd:指令类型
    • cmdsize: 指令长度
      CommandLC开头,不同的加载指令有不同的专有结构,但必包含cmd,cmdsize两个字段,用来定义指令类型以及指令长度

    指令类型:

    LC_SEGMENT/LC_SEGMENNT_64

    • 作用:将对应段中的数据加载并映射到进程的内存空间
    • 结构:
    
    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 (固定)
    cmdsize:指令长度
    segname:段名,如_PAGEZERO(保留数据空间,4g),_TEXT(代码数据),_DATA(函数指针),_LINKDIT(链接数据)
    vmaddr:段的虚拟内存起始地址
    vmsize:段的虚拟内存大小
    fileoff:段在文件中的偏移量
    filesize:段在文件中的大小
    maxprot:表示页面所需要的最高内存保护
    initprot:表示页面初始的内存保护
    nsects:当前segment有多少个sections
    flags:表示段的标志信息

    • MachOView实例:
      image.png

    段数据加载并映射到内存过程:从file offset处加载file size大小到虚拟内存vm addr处,并占用虚拟内存大小为vm size,一般情况下段名_TEXT, _DATAfile size = vmsize;段名_LINKDIT 的 file size < vmsize动态链接申请的内存控件要大于文件大小).

    LC_SEGMENT_64(_PAGEZERO): 是一个不可读、不可写、不可执行的空间,能够在空指针访问时抛出异常。这个段的大小,32位上是0x4000,64位上0000000100000000也就是 4G,4GB 并不是文件的真实大小,但是规定了进程地址空间的前 4GB 被映射为 不可执行、不可写和不可读,是从0(也是NULL指针的位置)开始的,这就是为什么当读写一个 NULL 指针或更小的值时会得到一个 EXC_BAD_ACCESS 错误。

    C、OC语言来解释,我们平时所操作的对象其实是一个指针,指针是指向另一块存储区域的变量。当向一个对象发送消息,指向这个对象的指针需要被使用,也就是你获得了指针指向的内存地址并且可以访问该内存块的值。当系统不再为你映射该内存块时,换句话说,该内存块已经不能够被你所使用,则不可以再次访问该内存块。 如果再次访问这块内存,发生这种情况时,内核会发送一个异常(EXC),表明您的应用程序无法访问该内存块(BAD ACCESS

    LC_SEGMENT_TEXT

    这是程序的代码段,该段是可读可执行,但是不可写

    • 作用:代码段,其中_stub_helper用于关联函数bind/rebind

    LC_SEGMENT_DATA

    可读/可写的数据段

    • 作用:函数指针,其中_la_symbol_ptr动态函数个数,及相对动态符号表的偏移量

    LC_SEGMENT_LINKEDIT

    • 作用:动态链接加载指令,支持动态链接dyld,该段长度覆盖符号表等数据(计算链接时程序的基址),符号表,动态符号表,字符串表段中定义的offset偏移量都是基于_LINKEDITvm_add
    • 结构:与LC_SEGMENT一致
    • MachOView实例:
    image.png

    LC_DYLD_INFO_ONLY

    dyld_info_command

    LC_DYLD_INFO_ONLYLC_DYLD_INFO是同一个结构

    image.png

    这个commanddyld在将二进制文件装载到内存链接的时候使用的

    • rebase:由于Macho被加载到内存的时候首地址不是固定的,是随机分配的,针对这个做修正的;
    • bind:在链接的时候对一些符号进行绑定的,比如我们用到了UIKIT框架的api,但是二进制中又没有这个符号,此刻就是做这个对应的工作;
    • lazy_bind:就是一开始不必要立即绑定,后面用到的时候再绑定

    LC_SYMTAB

    • 作用:符号表信息,解析函数名
    • 结构:
    image.png
    
    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:符号表偏移量,如一个函数对应一个符号
    nsyms:符号表中表个数
    stroff:字符串表偏移量,连续的内存空间用来存储函数名
    strsize:字符串表的大小

    • 查看 ```

    上面说到symbol的偏移量为115298320也就是0x6DF5010

    image.png

    Symbol Table装着都是结构nlist_64或者nlist可以,在<mach-o/nlist.h`中

    struct nlist {
        union {
    #ifndef __LP64__
            char *n_name;   /* for use when in-core */
    #endif
            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 */
        int16_t n_desc;     /* see <mach-o/stab.h> */
        uint32_t n_value;   /* value of this symbol (or stab offset) */
    }
    
    
    1. n_strx: 在String Table中的索引值;
    2. n_type: 可选的值有N_STAB、N_PEXT、N_TYPE、N_EXT
    3. n_sectsection的类型,要么就是NO_SECT
    4. n_desc
    5. n_value: 符号对应的地址, 5E3C 就是后面那个

    LC_DYSYMTAB

    • 作用:动态符号表信息,地址值为动态函数相对符号表的索引,_la_symbol_ptr对应的cmd可以换算出第一个动态函数对应动态符号表的初始地址,其次存储是连续,结构长度固定的,可以通过遍历获取所有动态函数的对应的符号表索引
    • 结构:
    
    struct symtab_command {
        uint32_t cmd;    /* LC_DYSYMTAB */
        uint32_t cmdsize;    /* sizeof(struct dysymtab_command) */
        uint32_t indirectsymoff; /* file offset to the indirect symbol table */
        uint32_t nindirectsyms;  /* number of indirect symbol table entries */
        .....
    }
    
    
    • 主要字段说明:

    indirectsymoff:动态符号表偏移量
    nindirectsyms:动态符号表中表个数

    • Indirect Symbols
    image.png

    这个Indirect Symbols包含了所有和动态库相关的符号,包括__DATA,__la_symbol_ptr__DATA,__nl_symbol_ptr__DATA,__got,这个表有以下用处:

    通过这个表的Symbol可以找到在符号表Symbol Table的位置,从而在字符串表String Table中找到名称;
    通过这个表的Indirect Address可以在__DATA,__la_symbol_ptr、__DATA,__nl_symbol_ptr、__DATA,__got中找到方法的地址

    LC_MAIN

    指定了main函数的入口地址

    image.png

    LC_LOAN_DYLIB

    描述了一些动态库相关的信息

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

    LC_RPATH

    Runpath的简写,程序运行链接路径

    image.png

    xcode中查看

    image.png

    LC_FUNCTION_STARTS

    方法是从哪里开始的

    image.png

    对应

    image.png

    LC_CODE_SINGATURE

    签名相关的信息

    image.png

    对应


    image.png

    section

    section对应SEGMENTDDATA数据
    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 */
    };
    
    
    

    字段解释

    1. sectname:当前section的名字;
    2. segname:位于哪个segment;
    3. addr:当前section在内存中的地址;
    4. size:当前的section所占的内存大小;
    5. offset:当前section的偏移量;
    6. reloff: 暂时没找到实际的用处
    7. nreloc:这个就是表示上面reloff的数量;
    8. flags: 这个是当前section的标志位,包括sectionType和sectionAttribute,一个section可以有多个属性,但是只能有一个类型,这个很好理解了,可以通过位运算分别获取类型和属性,(section->flags & SECTION_TYPE、section->flags & SECTION_ATTRIBUTES
    9. reserved1:保留字段,它可以表示偏移量也可以用来表示索引,一般用来表示Indirect Symbol ndex也就是间接索引表的位置,你可以在__got、__subs等中可以查看;
    10. reserved3:保留字段,一般表示数量的,比如在__subssection中就表示subs的个数;
    11. reserved3:保留字段了,暂时没什么用处

    补充

    • LC_DYSYMTAB(动态符号表)是LC_SYMTAB(符号表)的子集,LC_DYSYMTAB存储动态符号对应在LC_SYMTAB的序号
    • LC_SEGMENT(_TEXT):存储函数代码,stub_help取得绑定信息(即函数地址与符号的对应关系)
    • LC_SEGMENT(_DATA):函数地址存放在LC_SEGMENT(_DATA)内的la_symbol_ptr,第二次调用,直接通过la_symbol_ptr找到函数地址,不在需要繁琐的获取函数地址过程。
    • la_symbol_ptr对应section中的reserved1字段指明相对应的indirect symbol table起始的index

    程序运行时,动态链接的函数a地址记录在LC_SEGMENT(_DATA )la_symbol_ptr中。初始化时,程序只知道函数a的符号名而不知道函数的实现地址;首次调用,程序通过LC_SEGMENT(_TEXT)stub_help获取绑定信息;dyld_stub_binder来更新la_symbol_ptr中的符号实现地址;再次调用,直接通过la_symbol_ptr获取函数实现

    image.png

    动态函数调用过程

    程序初始化
    函数符号名:已存在,并以nlist结构存储,但nlist->n_value=0(函数地址没有值)
    函数实现地址:已存在,存放在mach-o文件cmd加载指令(SegmentName=_DATA,SectionName=__la_symbol_ptr)
    函数符号名与实现地址关联(未关联):即补全nlist信息

    程序运行:
    函数首次调用:
    函数符号名与实现地址进行关联,nlist->n_value赋值函数地址

    函数再次调用:
    通过关联信息,通过函数符号直接获得函数实现地址.

    关联过程如下

    寻址:处理器根据指令中给出的地址信息来寻找有效地址的方式

    介绍关联过程前,简单介绍几个基础知识

    • MACH-O的加载指令(__DATA,__la_symbol_ptr

    作用:存储了对应MACH-O文件所有的动态函数实现地址,且以连续内存地址存储
    字段解析:
    Szie:指令大小,可通过换算动态函数个数,如Szie/Szieof(void*)
    Reserved1:相对动态符号表dlsymbol_Tab偏移量,用来换算第一个动态函数对应动符号表的地址.

    根据文件的首地址加上符号的偏移地址,我们可以得出符号在内存中的地址

    • Indirect Symbol Table

    作用:连续的存储动态函数对应符号表的索引
    地址寻址:第一个动态符号寻址 = 动态符号基址(vm_add+slide) + Reserved1
    地址值:对应符号Symbol的索引值index

    • Symbol Table

    作用:以nlist结构连续存储函数符号与地址的关联关系,nlist包含函数实现地址,字符串偏移地址(计算函数名)
    地址寻址:符号表基址(vm_add+slide)+ index
    地址值:nlist

    • String Table

    作用:存储MACH-O文件所有的函数名,char*,每个函数名以\0分隔
    地址寻址:字符串表基址(vm_add+slide)+ 偏移量(nlist-> n_un->n_str
    地址值:函数名

    关联过程

    1. 遍历Mach-O文件下的所有LoadCommand,寻址目标cmd,搜索条件(SegmetName=__DATA,SectionName=__la_symbol_ptr),目标cmd存储了动态函数个数,及第一个动态函数相对动态符号表偏移量
    2. 动态函数个数及动态符号表偏移量:动态函数个数=cmd->Szie/Szieof(void*);
      动态符号表偏移量=cmd->reserved1
    3. 动态函数符号表寻址:第一个动态符号地址=动态符号表基址_LC_ DYSYMTAB->vm_add+slide+ reserved1;由于动态符号表连续存储动态函数符号,可遍历所有动态函数符号地址;地址值存储对应动态函数的符号表索引
    4. 关联建立,补全nlist结构体: 函数地址 + reserved1--->动态符号表寻址--->符号表index--->nlist信息补全

    相关文章

      网友评论

          本文标题:06---MachO

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