当前位置:   article > 正文

MachO && dyld(四)_xnu 源码阅读

xnu 源码阅读

XNU 加载 MachO 和 dyld 的流程

  • 加载流程总结

    XNU 加载 MachO 可执行文件dyld 动态链接器文件的大体流程其实很简单:
    创建进程 -> 创建虚拟内存空间 ->
    解析和映射 MachO 可执行文件 ->
    解析和映射 dyld 动态链接器文件 ->
    进入动态链接器 dyld 的执行流程
    XNU 加载 MachO 和 dyld 的流程
    以下只是简述了 XNU 加载 MachO 可执行文件dyld 动态链接器文件 的大体的流程
    下文的分析中,需要大家根据源码和注释进行反复多次理解,这样才能真正了解整个流程和这个流程中的细节

  • XNU 源码版本

    本文所使用的 XNU 源码版本为:xnu-6153.61.1
    可以到苹果官网下载相应版本的 XNU

  • XNU 源码分析

    ① 首先,我们来到:xnu - 6153.61.1\bsd\kern\kern_exec.c 找到 load_init_program(...) 函数

    load_init_program(...) 函数用于加载系统的初始化进程 launchd
    launchd 是一个用于管理进程的后台守护进程(daemon:守护进程的意思)
    在非 Debug 模式下,只会加载 "/sbin/launchd"
    参数 proc_t p 表示由操作系统底层创建出来的原始进程
    load_init_program(...) 函数
    其中,init_programs[] 就是用于存储守护进程路径的数组
    内核的 debug 模式下,可以加载用于调试的 launchd.debuglaunchd.development
    非 debug 模式下,只加载 launchd,用于进程管理
    init_programs 数组
    load_init_program_at_path(...) 函数用于验证输入参数和前置条件
    并构造用于调用 execve(...) 函数的参数
    load_init_program_at_path(...)
    execve(...) 函数底层调用 __mac_execve(...) 函数,用于:
    在父进程中 fork 出一个子进程,并在子进程中调用 exec 函数启动新的程序(即执行一个新的程序)
    execve(...) 与 __mac_execve(...)

    exec_activate_image(...) 函数用于按照文件的格式分发内存映射的函数,目前支持的文件的格式有 3 种:

    1. 单指令集 MachO 可执行文件
    2. 多指令集 MachO 可执行文件,即 Fat Binary(如果是 Fat Binary,则会先进行指令集级别的 MachO 分解,然后再循环调用 execsw(...) 函数进行内存映射)
    3. Shell 脚本

    上层也可直接调用 posix_spawn(...) 函数生成新进程,posix_spawn(...) 函数会自动调用 exec_activate_image(...) 函数:
    exec_activate_image(...)
    数组 execsw[] 的定义如下:
    struct execsw 结构体数组
    exec_mach_imgact(...) 函数主要完成了以下几个过程:

    1. vfork(...) 函数生成新的线程(vfork(...) 函数会生成进程,但不会生成线程)
    2. 把 MachO 可执行文件映射进内存(调用的子函数 load_machfile(...)
    3. 签名、uid 等权限处理,dyld 相关的处理工作
    4. 释放资源

    exec_mach_imgact(...)
    exec_fat_imgact(...) 函数用于处理胖二进制文件(通用二进制文件)
    exec_shell_imgact(...) 函数用于处理 Shell 脚本
    这两个函数不是重点,我们只看一下它俩的定义:
    exec_fat_imgact(...)
    exec_shell_imgact(...)
    load_machfile(...) 函数位于 \xnu - 6153.61.1\bsd\kern\mach_loader.c 中,用于 MachO 可执行文件的加载(不包含 MachO 可执行文件的解析):

    1. 为当前 task 分配可执行内存,task 是一个任务实例,负责进程内的虚拟内存空间,线程管理等工作
    2. MachO 和 dyld 的 ASLR 偏移量 的随机
    3. exec_mach_imgact(...) 函数回传结果

    load_machfile(...)
    其中, vm_map_create(..) 函数用于创建虚拟内存映射,底层调用了 vm_map_create_options(..) 函数
    vm_compute_max_offset(...) 函数则用于获取不同 CPU 架构的最大的虚拟内存空间
    这两个函数都位于 \xnu - 6153.61.1\osfmk\vm\vm_map.c
    vm_map_create(..) + vm_map_create_options(..)
    在这里插入图片描述
    parse_machfile(...) 函数主要完成了以下几个工作:

    1. MachO 可执行文件的解析,相关 segment 虚拟内存分配
    2. dyld 动态链接器文件的加载(调用的子函数 load_dylinker(...)
    3. dyld 动态链接器文件的解析及虚拟内存分配

    注意:内核(XNU)并不关心 MachO 中具体 Section(节)的内容,即不解析 Section 的具体信息,而是以 Segment(段) 为单位进行映射
    parse_machfile(...)
    load_dylinker(...) 函数用于加载动态链接器 dyld 的可执行文件
    并调用步骤 ⑦ 中的 parse_machfile(...) 函数,进行 dyld 的解析与虚拟内存分配:
    load_dylinker(...)

  • App 在虚拟内存中的分布

    虚拟内存 : 我们开发者在开发过程中所接触到的内存均为虚拟内存,虚拟内存使 App 认为它拥有连续可用的内存空间(一个连续完整的地址空间),而实际上,App 通常是分布在多个物理内存碎片中,系统的虚拟内存空间映射表(vm_map)负责管理虚拟内存和物理内存的映射关系

    文中所提的内存均为虚拟内存。共享动态库其实就是共享的物理内存中的那份动态库,App 虚拟内存中的共享动态库并未真实分配物理内存,使用时虚拟内存会访问同一份物理内存达到共享动态库的目的

    iPhoneXr 的物理内存(RAM)只有3GB,那么当超过 3GB 的物理内存时,iOS 是如何处理的呢?
    系统会使用一部分硬盘空间(ROM)来充当内存使用,在需要时进行数据交换。当然,硬盘的数据交换速度是远远慢于物理内存的,这也是我们内存过载时,App 卡顿的原因之一

    ASLR : Address Space Layout Randomization,是一种针对缓冲区溢出漏洞的安全保护技术,通过对 堆、栈、共享库映射等 线性区布局的随机化,通过增加攻击者预测目的地址的难度,达到保护的目的。注意:ASLR 只是随机了线性区布局的起始地址,而不改变线性区里面内容的顺序

    Arm64 架构为例,根据上面对 XNU 源码的分析可知:

    1. App 最大的虚拟内存空间为 64GB
    2. 空指针陷阱段的默认大小为 4GB
    3. MachO 执行文件、dyld 动态链接器文件、共享动态库 都有独立的 ASLR 偏移量

    我们大体可以得出一个 App 的虚拟内存分布,如下图所示:
    App 虚拟内存空间分布

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/891270
推荐阅读
相关标签
  

闽ICP备14008679号