iOS技术积累

不管生活有多不容易,都要守住自己的那一份优雅。

深入理解Mach-O文件格式_3.md

动态链接和加载是怎么做的

  • 特殊的 LC_SEGMENT_64 __LINKEDIT

链接信息段用于存储dyld需要用到的信息包括:符号表、字符串表、重定位项表、 等 是一系列的数据综合
与代码段和数据段在 Data(数据区域)规整化一的存储结构不同的是,因为链接信息段存储着诸多类型不同的用于动态链接的加载命令(LoadCommands)所需要的数据,所以链接信息段在 Data(数据区域)的存储结构,会根据具体加载命令(LoadCommands)的不同而不同
以下加载命令需要额外的空间用于存储数据,其数据存储在__LINKEDIT 区域的数据部分:
01.LC_DYLD_INFO_ONLY
02.LC_SYMTAB
03.LC_DYSYMTAB
04.LC_FUNCTION_STARTS
05.LC_DATA_IN_CODE
06.LC_CODE_SIGNATURE
以下加载命令不需要额外的空间用于存储数据,其将所有信息存储在 LoadCommands 区域的加载命令本身:
01.LC_LOAD_DYLINKER
02.LC_UUID
03.LC_VERSION_MIN_IPHONEOS / LC_VERSION_MIN_MACOSX
04.LC_SOURCE_VERSION
05.LC_LOAD_DYLIB
06.LC_RPATH

iOS 15 以下传统的 LC_DYLD_INFO

主要包含 rebase bind weakbind lazybind,export info 下面分段讲解

 // 所有的 信息按 字节序编码,不需要大小端转换
struct dyld_info_command {
   uint32_t   cmd;      /* LC_DYLD_INFO or LC_DYLD_INFO_ONLY */
   uint32_t   cmdsize;      /* sizeof(struct dyld_info_command) */
   ....
}

这里解释下,rebase 和 bind的区别。
在动态链接过程,
代码访问其他代码分为四种情况

  1. 代码访问同模块的代码,只需要 pc 相对访问即可,无需静态和动态重定位
  2. 代码访问同模块的数据,只需要 pc + 偏移即可, 无需静态和动态重定位
  3. 代码访问外部模块的数据,只需要 got 表,需要动态重定位,也就是绑定
  4. 代码访问外部模块的带阿米,只需要 got 表, 需要动态重定位,也就是绑定
    至于 weak 绑定是绑定的 cxx 特殊case
    lazy 绑定则是 got + plt 表为了优化性能的特殊case

那么数据对数据的访问不是指令,则需要 rebase 。比如如下代码
在静态链接的时候 p 执行 a在data段的绝对地址,但是 由于有了可执行文件加载的 ALSR 机制 再加上动态库加载地址本来就不是固定的加载地址,因此在 load 的时候
进行 rebase , Mach-O 里面最常见的就是 __attribute(constructor) 的数据了 存的是个绝对地址,启动需要rebase。

static int a = 101;
static int *p = &a;

rebase

每当 dyld 将图像加载到与其首选地址不同的地址时 (ASLR),Dyld 都会对 image 进行 rebase 。(换句话说没有 ASLR 就没有rebase)
每次 rebase 其实需要三列元信息
<seg-index, seg-offset, type> (哪个segement,其实偏移,操作类型)
但为了压缩信息,每次rebase 会复用三列数据,那个变化了就更改那个列,
比如 起始给定了(0,1,1) 后面假设 type 需要调整到2就会给一个调整type的指令,后面紧跟2. 那么这个元组就变成了 (0,1,2)
rebase 的定义在 dyld的 template <typename P> void Adjustor<P>::adjustDataPointers() 方法内

struct dyld_info_command {
  ...

    uint32_t   rebase_off;  /* file offset to rebase info  */
    uint32_t   rebase_size; /* size of rebase info   */
    ...
}
1字节指令解码
 低 4 位是**立即数type**
 高 4 位是**rebase指令类型**
 掩码分别是
#define REBASE_OPCODE_MASK                  0xF0
#define REBASE_IMMEDIATE_MASK                   0x0F

/*
 * reabse的数据是什么类型
 */
#define REBASE_TYPE_POINTER                 1 // 指针类型
#define REBASE_TYPE_TEXT_ABSOLUTE32             2 //绝对地址
#define REBASE_TYPE_TEXT_PCREL32                3 // pc相对访问
/*
 * 操作码 类型
 */
// 
#define REBASE_OPCODE_DONE                  0x00 // rebase 指令结束
// 设置type,高位是1,是设置指令,后四位是离立即数,上面也就3种type够用了
#define REBASE_OPCODE_SET_TYPE_IMM              0x10 // 立即数

/* 设置segent列,后面紧跟 1个 uleb 编码位 复制给offset
dyld源码为
case REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
    segIndex = immediate;
    segOffset = read_uleb128(diag, p, end);
*/
#define REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB       0x20 
/* ,后面紧跟 1个 uleb 编码位 复制给offset
dyld源码为
case REBASE_OPCODE_ADD_ADDR_ULEB:
    segOffset += read_uleb128(p, end);

*/
#define REBASE_OPCODE_ADD_ADDR_ULEB             0x30 // 设置offset立即数,
/* ,后面紧跟 1个 uleb 编码位 * rebase 长度(比如8位指针)复制给 offset
dyld源码为
case REBASE_OPCODE_ADD_ADDR_IMM_SCALED:
    segOffset += immediate*sizeof(pint_t);
    break;

*/
#define REBASE_OPCODE_ADD_ADDR_IMM_SCALED           0x40 //  
/* 真正的rebase 操作 do 立即数 次rebase操作
dyld源码为
case REBASE_OPCODE_DO_REBASE_IMM_TIMES:
    for (int i=0; i < immediate; ++i) {
        slidePointer(segIndex, segOffset, type);
            segOffset += sizeof(pint_t);
        }

template <typename P>
void Adjustor<P>::slidePointer(int segIndex, uint64_t segOffset, uint8_t type)
{
    cache_builder::ASLR_Tracker* aslrTracker = this->_mappingInfo[segIndex].aslrTracker;
    pint_t*   mappedAddrP  = (pint_t*)((uint8_t*)_mappingInfo[segIndex].cacheLocation + segOffset);
    uint32_t* mappedAddr32 = (uint32_t*)mappedAddrP;
    pint_t    valueP;
    uint32_t  value32;
    switch ( type ) {
        case REBASE_TYPE_POINTER:
            valueP = (pint_t)P::getP(*mappedAddrP);
            核心在这里设置为  valueP + slideForOrigAddress(valueP)
            P::setP(*mappedAddrP, valueP + slideForOrigAddress(valueP));
            aslrTracker->add(mappedAddrP);
            break;

        case REBASE_TYPE_TEXT_ABSOLUTE32:
            value32 = P::E::get32(*mappedAddr32);
            P::E::set32(*mappedAddr32, value32 + (uint32_t)slideForOrigAddress(value32));
            break;

        case REBASE_TYPE_TEXT_PCREL32:
            // general text relocs not support
        default:
            _diagnostics.error("unknown rebase type 0x%02X in %s", type, _dylibID);
    }
}
*/
#define REBASE_OPCODE_DO_REBASE_IMM_TIMES           0x50 
/* 同上,只不过立即数编码不够,后面紧跟 1个 uleb 编码位 
然后do ulebnum 次rebase操作
case REBASE_OPCODE_DO_REBASE_ULEB_TIMES:
    count = read_uleb128(p, end);
    for (uint32_t i=0; i < count; ++i) {
        slidePointer(segIndex, segOffset, type);
        segOffset += sizeof(pint_t);
    }
    break;
 */
#define REBASE_OPCODE_DO_REBASE_ULEB_TIMES          0x60 
/** 做一次rebase 并且后面紧跟 uleb 数复制给 segoffset
case REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB:
    slidePointer(segIndex, segOffset, type);
    segOffset += read_uleb128(p, end) + sizeof(pint_t);
 */
#define REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB           0x70 
/*
// 跳过多少次,后面通常跟 count  和 skip 然后做 count 次 rebase,并且每次 对 segent 做
skip + sizeof(ptr) 个数
case REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB:
    count = read_uleb128(p, end);
    skip = read_uleb128(p, end);
    for (uint32_t i=0; i < count; ++i) {
        slidePointer(segIndex, segOffset, type);
        segOffset += skip + sizeof(pint_t);
    }

*/
#define REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB    0x80 和 skip 

这次估计你能看懂rebas代码了

bind

struct dyld_info_command {
  ...
  /*
   同 rebase 信息一样,但是这次需要的是 5元组

` <seg-index, seg-offset, type, symbol-library-ordinal, symbol-name, addend>`
`(seg索引,segoffset,类型,符号来源的动态库以1开头,符号名,加数)`

    uint32_t   bind_off;    /* file offset to binding info   */
    uint32_t   bind_size;   /* size of binding info  */

    ...
}
1字节指令解码
 低 4 位是**立即数type**
 高 4 位是**rebase指令类型**
 掩码分别是
#define BIND_OPCODE_MASK                    0xF0
#define BIND_IMMEDIATE_MASK                 0x0F

/*
 * 绑定的类型
 */
#define BIND_TYPE_POINTER                   1  // 指针类型
#define BIND_TYPE_TEXT_ABSOLUTE32               2 //绝对地址
#define BIND_TYPE_TEXT_PCREL32                  3 // pc相对访问

// 特殊查找序号,分别是自己,主二进制,平坦的动态查找和弱符号冲突
#define BIND_SPECIAL_DYLIB_SELF                  0
#define BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE          -1
#define BIND_SPECIAL_DYLIB_FLAT_LOOKUP              -2
#define BIND_SPECIAL_DYLIB_WEAK_LOOKUP              -3

#define BIND_SYMBOL_FLAGS_WEAK_IMPORT               0x1 weak符号 0b0001
#define BIND_SYMBOL_FLAGS_NON_WEAK_DEFINITION           0x8 非weak 符号 0b1000

#define BIND_OPCODE_DONE                    0x00  // 绑定结束
#define BIND_OPCODE_SET_DYLIB_ORDINAL_IMM           0x10 // 修改 bibary 序号 数存在指令里面
#define BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB          0x20 // 修改 bibary 序号 后面跟一个 uleb 数
#define BIND_OPCODE_SET_DYLIB_SPECIAL_IMM           0x30 // 绑定特殊数字,就是起那么说的负数,或者0 比如 绑定 self符号,主二进制符号,平坦的动态查找,或者weak符号冲突检测
#define BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM       0x40 // 修改flag看是否是若符号
#define BIND_OPCODE_SET_TYPE_IMM                0x50// 修改 type 数存在指令里面
#define BIND_OPCODE_SET_ADDEND_SLEB             0x60 // 修改加数,后面跟 uleb 数
#define BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB         0x70 // 修改 segement index 数存在指令里面,后面跟一个uleb 修改 offset
#define BIND_OPCODE_ADD_ADDR_ULEB               0x80 // 修改offset 后面跟一个 uleb 
#define BIND_OPCODE_DO_BIND                 0x90 // 真正的绑定 把 5元组传递下去
#define BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB           0xA0 //绑定完加一个 offset uleb数
#define BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED         0xB0 // 绑定完添加 offset  segmentOffset += immediate*ptrSize + ptrSize;
#define BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB        0xC0 // 后面跟 count 和 skip 做 count 次绑定,每次 offset 做跳过 segmentOffset += skip + ptrSize;
#define BIND_OPCODE_THREADED                    0xD0 // 后面必须跟 后两个 子命令
#define BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB 0x00 // 修改tablecount 没见过这是干啥的。不太看得懂 感觉像是 iOS 15 优化
#define BIND_SUBOPCODE_THREADED_APPLY                0x00 // 链式绑定 addChainStart(segmentIndex, segIndexSet, segmentOffset, DYLD_CHAINED_PTR_ARM64E, stop);

这次你可以看懂这个了把,例子中第一个还是个weak绑定。后面有元组信息发生变化就单独修改某个数据然后继续绑定。 大部分都是把 got表的值从 0(占位符)改成真正的值。

weak_bind

某些 C++ 程序要求 dyld 具有唯一符号,以便进程中的所有二进制都使用某些代码/数据的相同副本。
weak绑定的操作符 和bind一样,但是他是按符号名字母需排序的。
weak绑定的动作在 bind 之后统一处理
dyld 能够按顺序遍历具有弱绑定信息的所有图像并寻找冲突,如果没有冲突就不更新了,如果有冲突就更新
听起来很拗口那么有啥用?
用处就是比如一开始所有的对象的 "operator new" 绑定到了 libstdc++.dylib
后面有个动态库覆盖了 "operator new" 符号,那么所有的对这个符号的依赖会自动转向这个库,如果后面还有动态库,那么继续覆盖。
感觉是为了实现一些hook的手法。

struct dyld_info_command {
  ...
   uint32_t   weak_bind_off;    /* file offset to weak binding info   */
    uint32_t   weak_bind_size;  /* size of weak binding info  */

}

lazy_bind

有一些符号不是需要立即绑定的可以推迟到首次使用绑定
lazy_bind info 和前面一样也是一堆 BIND Opcodes
但是通常来说 dyld 会忽略绑定这个符号, ld64(静态连接器)这里面套用了模版代码,让他们执行 懒加载桩,并且吧自己的偏移当做参数计算进去

struct dyld_info_command {
  ...
    uint32_t   lazy_bind_off;   /* file offset to lazy binding info */
    uint32_t   lazy_bind_size;  /* size of lazy binding infs */
    ...
}

export Info

  1. 导出的外部符号
    dylib 导出的符号在是一个 trie 树 (或者交字典树)中进行编码。
    导出区域是Trie 节点流。 第一个节点依次是 trie 的起始节点。

符号的节点以 uleb128 开头,它是到目前为止该字符串导出的符号信息的长度。

  • Case1 如果没有导出的符号,则节点以零字节开始。
    后面紧跟一个 childCount,( NodeLable(CString风格),NextNode offset)* childcount

  • case 2 如果有导出信息 后面继续跟

  1. 首先是一个 uleb128,其中包含 flags 。通常情况下,flag后面是一个 uleb128 数的偏移量,。如果flag是 EXPORT_SYMBOL_FLAGS_REEXPORT 则后面的是一个 uleb128 动态库序数,然后跟一个以 0结尾的字符串,如果字符串长度是0,则要去这个动态库重新找这个符号。
    如果flag 是EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER 后面跟两个 uleb128 数,stuboffset, 和 resolver offset , stub 是 -nonlazy符号。resolve 是一个必须被调用的函数,调用完填充给lazy符号
  2. 在可选的导出符号信息之后是一个字节,表示离开该节点的边的数量(0-255),然后是每个边。
    每个边都是一个以零结尾的 UTF8 字符串,表示符号的额外字符,然后是一个 uleb128 offset量,指向该边指向的子节点。


    uint32_t   export_off;  /* file offset to lazy binding info */
    uint32_t   export_size; /* size of lazy binding infs */
};
/*
 * 符号标记
 */// 2 位掩码
#define EXPORT_SYMBOL_FLAGS_KIND_MASK               0x03

#define EXPORT_SYMBOL_FLAGS_KIND_REGULAR            0x00
#define EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL           0x01
#define EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE           0x02

#define EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION         0x04
#define EXPORT_SYMBOL_FLAGS_REEXPORT                0x08
#define EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER           0x10
#define EXPORT_SYMBOL_FLAGS_STATIC_RESOLVER         0x20

iOS 15

当你的 iOS 库最低部署版本被升级到 iOS 15 后 静态链接器链接后的动态库和可执行文件不再有 LC_DYLD_INFO_ONLY 而是变成了
LC_DYLD_EXPORTS_TRIELC_DYLD_CHAINED_FIXUPS
其中
LC_DYLD_EXPORTS_TRIE 和之前一样 是一颗 Trie 树,提供外部符号
LC_DYLD_CHAINED_FIXUPS 将以前的 rebase bind(bweakind) 合并为一个链式结构,i
PS lazybind已废弃
并且仅一次 pagefault 就可完成 reabse bind,。
详细的可以参考iOS 15 如何让你的应用启动更快


各个 Section 都是干嘛额

Segement64

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];    /* 段名字 */
    uint64_t    vmaddr;     /* vmaddr 段的虚拟内存起始地址 */
    uint64_t    vmsize;     /* vmsize 段的虚拟内存大小 */
    uint64_t    fileoff;    /* 段在文件中的偏移量 */
    uint64_t    filesize;   /*  段在文件中的大小 */
    vm_prot_t   maxprot;    /* 段页面所需要的最高内存保护 */
    vm_prot_t   initprot;   /* 段页面初始的内存保护 */
    uint32_t    nsects;     /* 段中包含 section 的数量 */
    uint32_t    flags;      /* 标志位 */
};

struct section_64 { /* for 64-bit architectures */
    char        sectname[16];   /* sectname section 名 */
    char        segname[16];    /* segname 该 section 所属的 segment 名 */
    uint64_t    addr;       /* addr 该 section 在内存的启始位置 */
    uint64_t    size;       /* size 该 section 的大小 */
    uint32_t    offset;     /* offset 该 section 的文件偏移*/
    uint32_t    align;      /* align 字节大小对齐 2的align的次方 */
    uint32_t    reloff;     /* 重定位入口的文件偏移 */
    uint32_t    nreloc;     /* 需要重定位的入口数量s */
    uint32_t    flags;      /* 包含 section 的 type 和 attributes*/
    uint32_t    reserved1;  /* reserved (for offset or index) */
    uint32_t    reserved2;  /* reserved (for count or sizeof) */
    uint32_t    reserved3;  /* reserved */
};

Segement 和 section 名称对链接器没啥用,但对人有理解价值
下面是一些常见的


#define SEG_PAGEZERO    "__PAGEZERO"    /* 可执行文件空访问保护段 */


#define SEG_TEXT    "__TEXT"    /* 传统的 unix 代码 segement*/
#define SECT_TEXT   "__text"    /* mach-o 更传统的部分 */

#define SEG_DATA    "__DATA"    /* 传统的 unix 数据 segement */
#define SECT_DATA   "__data"    /* mach-o 更传统的部分 */
                    /* no padding, no bss overlap */
#define SECT_BSS    "__bss"     /* segement 没有文件部分,但是有内存*/
#define SECT_COMMON "__common"  /* 编译器全局符号初始化值*/

#define SEG_OBJC    "__OBJC"    /* objective-C runtime segment */
#define SECT_OBJC_SYMBOLS "__symbol_table"  /*  objc 符号表 */
#define SECT_OBJC_MODULES "__module_info"   /*  objc 模块信息 */
#define SECT_OBJC_STRINGS "__selector_strs" /* objc selector 字符包 */
#define SECT_OBJC_REFS "__selector_refs"    /* objc selector 引用的字符包 */

#define SEG_LINKEDIT    "__LINKEDIT"    /* 静态链接器创建的segement  Created with -seglinkedit option to ld(1) for 仅可执行文件或者动态库有,里面是 rebase bind weakrease 等动态加载库加载使用的信息 */

#define SEG_UNIXSTACK   "__UNIXSTACK"   /* unix stack*/

#define SEG_IMPORT  "__IMPORT"  /* dyld 特有的 segement 具有 读写可执行全局,*/

各个 Section 都是干嘛额

  • __TEXT,__text
    指令 section 这里面放的都是具体的机器码,(机器码可以反汇编,不能说是汇编,因为汇编是程序员编写并不是和指令一一对齐。) r-x 权限

  • __TEXT,__stubs
    函数调用桩,前面解释过 3个指令,用一种固定的模版代码获取got表或者lazy符号表里面供 dyld 填充的外部符号地址

  • __TEXT,__stub_helper
    前面提过,dyld函数桩辅助函数,执行 dyld dyld_stub_binder 绑定函数,目的是为了回填__DATA,__la_symbol_ptr,的函数指针

  • __TEXT,__cstring
    去重后的C 语言字符串

  • __TEXT,__constg_swiftt
    swift 语言类型描述符 ,Swift 源代码里面有,具体干啥不知道

  • __TEXT,__swift5_builtin
    swift5builtin反射描述信息,hopper 可以查看

  • __TEXT,__swift5_typeref
    里是其他sections里引用到的 Swift mangled type name

  • __TEXT,__swift5_reflstr
    swift meta 引用的字符传

  • __TEXT,__swift5_fieldmd
    该部分包含字段描述符数组。字段描述符包含单个类、结构或枚举声明的字段记录的集合。每个字段描述符可以有不同的长度,具体取决于类型包含的字段记录数

  • __TEXT,__swift5_types
    此部分包含 32 位有符号整数数组。每个整数都是一个相对偏移量,指向 TEXT.const 部分中的标称类型描述符。

  • __TEXT,__swift5_assocty
    该部分包含关联类型描述符的数组。关联类型描述符包含一致性的关联类型记录的集合。关联类型记录描述从关联类型到一致性的类型见证的映射

  • __TEXT,__swift5_proto
    此部分包含 32 位有符号整数数组。每个整数都是一个相对偏移量,指向 TEXT.const 部分中的协议一致性 (protocol comformance descripter)描述符。

  • __TEXT,__swift5_protos
    此部分包含 32 位有符号整数数组。每个整数都是一个相对偏移量,指向 TEXT.const 部分中的协议(protocol descriptor)描述符

  • __TEXT,__swift5_capture
    捕获描述符描述闭包上下文对象的布局。与普通类型不同,闭包上下文的泛型替换来自对象,而不是元数据

  • __TEXT, __swift5_mpenum
    实在不知道是啥

  • __TEXT,__swift5_replace
    本节包含动态替换(dynamic 函数)信息。这本质上是 Objective-C 方法调配的 Swift 等价物。

  • __TEXT,__swift5_replac2
    本节包含不透明类型的动态替换信息。目前尚不清楚为什么创建这个附加部分而不是 __swift5_replace。

  • __TEXT,__swift_hooks

  • __TEXT,__swift51_hooks

  • __TEXT,__swift56_hooks

  • __TEXT,__s_async_hooks
    启动的特殊hooks

  • __TEXT,__objc_methname
    objc 的 selector名称,以C字符风格存储

  • __TEXT,__objc_classname
    objc 的类名称,以C字符风格存储

  • __TEXT,__objc_methtype
    objc 的typeencoding名称,以C字符风格存储

  • __TEXT,__objc_stub
    objc 的函数桩,
    +__ETEXT

  • __DATA, __literal16

  • __DATA, __literal8
    16 位 和 8位 浮点数字面量值

  • __DATA, __const
    编译时常量,里面都是具体的数据,由加载指令调用。
    Swift 的各种描述符数据也存在这里面
    Protocol conformance descriptor
    Module descriptor
    Protocol descriptor
    Nominal type descriptors
    Direct field offsets
    Method descriptors

  • __DATA,__got
    non-lazy 间接符号表

  • __TEXT,__unwind_info
    用来存储处理异常情况信息

  • __TEXT,__eh_frame
    用于异常处理

  • __DATA,__mod_init_func
    constructor 函数
    __DATA,__la_symbol_ptr
    lazy 符号指针,

  • __DATA,__objc_classlist
    Objective-C 类列表,以指针形式存储指向 __DATA,__data 数据

  • __DATA,__objc_nlclslist
    OC 的类的 +load 函数列表

  • __DATA,__objc_catlist
    Objective-C 分类列表,以指针形式存储指向 __DATA,__data 数据

  • __DATA,__objc_nlcatlist
    OC 的分类的 +load 函数列表

  • __DATA,__objc_protolist
    Objective-C 协议列表,以指针形式存储指向 __DATA,__data 数据

  • __DATA,__objc_imageinfo
    版本信息,

  • __DATA,__objc_const
    objc 编译时元数据, 包含协议描述信息,

  • __DATA,__objc_selrefs
    有那些 selector 被引用了

  • __DATA,__objc_classrefs
    有那些类被引用了

  • __DATA,__objc_protorefs
    有那些协议被引用了

  • __DATA.__objc_superrefs
    基础了那些父类

  • __DATA,__objc_data

  • objc 编译时元数据, 包含类,方法,描述信息 可以理解为 objc_class 的 ro_t,

  • __DATA,__objc_ivar

  • __DATA_DIRTY,__objc_ivar
    一些字典的优化 objc ivar 信息

  • __DATA,_data
    通用的数据区

没见过的有

  • __DATA,__crash_info

LC_SEGMENT_64(__PAGEZERO):空指针陷阱段

这是一个不可读、不可写、不可执行的空间,能够在空指针访问时抛出异常(用于捕捉对空指针的引用)
在 64 位的操作系统上,这个段的虚拟内存大小是 4GB
4GB 并不是指该段物理文件的真实大小,也不是指该段所占物理内存的真实大小
4GB 是规定了进程地址空间的前 4GB 被映射为:不可读、不可写、不可执行的空间
这就是为什么当读写一个 NULL(0x0) 指针时会得到一个 EXC_BAD_ACCESS 错误
因为 LC_SEGMENT_64(PAGEZERO) 的物理文件大小为 0
所以 Data(数据区域)中没有与 LC_SEGMENT_64(
PAGEZERO) 对应的部分


参考

  1. iOS 研习记—— 谈谈静态库与动态库
  2. iOS 研习记 聊聊 iOS 中的 Mach-O
  3. iOS之深入解析UmbrellaFramework的封装与应用
  4. iOS类加载流程(一):类加载流程的触发
  5. 逆向,插入一个 LC_ROUTINES执行些额外逻辑
  6. LEB128格式的说明
  7. OS Runtime源码解析initialize load attribute总结
  8. Fairplay DRM与混淆实现的研究
  9. iOS 上的自动链接( Auto Linking )
  10. 深入 iOS 静态链接器(一)— ld64
  11. iOS 逆向入门 - 动态库注入原理
  12. Dylib注入&劫持总结
  13. OS X平台的Dylib劫持技术(上)
  14. dyld详解
  15. LLD, THE LLVM Linker
  16. Atom Model Linker
  17. 深入研究了一下mach-o
  18. iOS 15 如何让你的应用启动更快
  19. About the LC_DYLD_INFO[_ONLY] command.
  • Swift 专项
  1. swift-类结构源码探寻(一)
  2. Swift metadata
  3. Mach-O 文件格式探索

评论卡