ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JITAllocator双映射技巧:mach_make_memory_entry_64+vm_map完整解读

JITAllocator双映射技巧:mach_make_memory_entry_64+vm_map完整解读 JITAllocator双映射技巧mach_make_memory_entry_64vm_map完整解读【免费下载链接】MadeiraRun x86-64 Windows PC games on jailed iOS via FEX-Emu Wine DXMT项目地址: https://gitcode.com/GitHub_Trending/mad/Madeira在 iPhone 上跑 x86-64 Windows 游戏最大的拦路虎是 iOS 对 JIT即时编译的严格限制。Madeira 项目的 JITAllocator 模块用了一个巧妙的双映射dual-mapping技巧通过mach_make_memory_entry_64创建一个命名的内存条目再用vm_map把同一块物理内存映射成可写视图和可执行视图两个地址从而绕过 W^X不能同时可写又可执行限制让 FEX-Emu 动态生成的 ARM64 代码既写得进、又跑得起来。本文将用通俗的方式完整解读这套机制。一、为什么需要双映射iOS 上的 JIT 困境 普通电脑上JIT 编译器比如 FEX-Emu 把 x86 指令翻译成 ARM64的做法很直接分配一块可写的内存把生成的机器码写进去把它切成可执行然后调用问题在于iOS 出于安全考虑强制执行W^X 策略——同一块内存不能既是可写又是可执行的。而且 iOS 只有在调试器附加CS_DEBUGGED标志置位时才允许执行动态生成的代码。JITAllocator的解法是让同一片物理内存拥有两个不同的虚拟地址视图视图权限用途RW 视图rw_ptr可读 可写FEX 在这里写入生成的 ARM64 代码RX 视图rx_ptr可读 可执行从这里执行生成的代码两个视图指向同一块物理页所以往 RW 地址写的每个字节RX 地址都能读到——代码写一次执行一次天然一致。这就是双映射的核心思想。二、三步走创建双映射区域的完整流程 核心函数 jit_region_create() 分三步完成这一切实现思路参考了 MeloNX 项目的做法第 1 步mach_make_memory_entry_64 创建命名内存条目kr mach_make_memory_entry_64( task, entry_size, 0, MAP_MEM_NAMED_CREATE | VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE, mem_entry, MACH_PORT_NULL);这一步向 Mach 内核申请一个命名的内存对象named memory object以 Mach portmem_entry为句柄。可以把它理解成先在系统里注册了一块逻辑内存拿到一个凭证。关键点是它的最大权限里包含了VM_PROT_EXECUTE——这个潜力为后续 RX 视图的执行权限埋下了伏笔。第 2 步vm_map 映射 RW 视图vm_map(task, rw_addr, size, 0, VM_FLAGS_ANYWHERE, mem_entry, 0, FALSE, VM_PROT_READ | VM_PROT_WRITE, // 当前权限 VM_PROT_READ | VM_PROT_WRITE, // 最大权限 VM_INHERIT_DEFAULT);用第 1 步的mem_entry作为源让内核在任意空闲地址VM_FLAGS_ANYWHERE映射出一个可读可写的视图。JIT 编译器之后所有memcpy写码操作都发生在这里。第 3 步vm_map 再映射一次得到 RX 视图vm_map(task, rx_addr, size, 0, VM_FLAGS_ANYWHERE, mem_entry, 0, FALSE, VM_PROT_READ | VM_PROT_EXECUTE, // 当前只读可执行 VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE, // 最大保留 W 权限 VM_INHERIT_DEFAULT);同一个mem_entry、同一个偏移 0vm_map再来一次内核就会在另一个地址上给这块物理内存开第二个窗口这次当前权限设为可读可执行。这里有个值得细品的细节见 JITAllocator.c 第 187-192 行注释RX 视图的最大权限故意保留写权限RWX。这样 Wine 运行时需要对某个 RX 页做临时修补时例如 FEX 的PatchCallChecker写__ImageBase RVA可以用vm_protect临时打开写权限内核通过 COW写时复制翻转该页为 R/W而当前权限依旧遵守 W^X。安全与灵活性兼得。 注意 iOS 的页大小是16KB代码中定义为JIT_PAGE_SIZE 0x4000与 Linux 上常见的 4KB 不同这也是很多移植到 iOS 的内存代码容易踩的坑。三、写入代码后别忘了刷新指令缓存 ⚡ARM 架构中数据缓存和指令缓存是分开的。往 RW 视图写完机器码后必须告诉 CPU指令缓存里的旧内容作废否则会执行到过期指令。jit_region_write() 封装了这个流程memcpy把代码写进RW 视图调用sys_icache_invalidate()失效RX 视图对应区间的指令缓存返回 RX 侧指针调用方直接当函数指针执行void *exec_ptr jit_region_write(region, 0, code, code_size); // 之后 func() 执行从 RX 视图读出的刚写入的 ARM64 指令配套的公开 API 见 JITAllocator.hjit_region_create/jit_region_rw_ptr/jit_region_rx_ptr/jit_region_write/jit_region_destroy接口干净地隐藏了底层 Mach 调用的所有细节。四、进阶技巧让 JIT 内存隐身于 Jetsam iOS 的 Jetsam 内存管理会按应用的物理内存占用footprint杀进程。游戏 JIT 池动辄数百 MB很容易被计入配额导致崩溃。JITAllocator用第二个私有 Mach API 解决它mach_memory_entry_ownership()对命名条目打上VM_LEDGER_FLAG_NO_FOOTPRINT标记使这块内存不计入应用的 Jetsam 配额。生产环境里大内存池并不直接走jit_region_create()而是由调试器分配 RX 页后再双映射所以项目提供了 jit_make_region_no_footprint()对已映射的区域补做豁免。它内部同样调用mach_make_memory_entry_64这次是对已有地址范围建条目拿到的是与两个别名共享的同一个 vm_object再逐组尝试不同的参数组合并前后采样 phys_footprint来用数据证明豁免是否真正生效——这种让日志说话的工程习惯很值得学习。五、自检机制不执行代码也能验证映射 ✅模块内置了完整的分级自测方便排查环境问题jit_test_mapping()实现见 L437-L491创建区域 → 验证 RW/RX 地址确实不同 → 往 RW 写0xDEADBEEF、从 RX 读回比对 → 用vm_region_64检查 RX 页是否有执行权限。全程不执行任何生成代码安全无副作用jit_test_execute()真正的功能测试写入mov x0, #42; ret两条 ARM64 指令并执行返回 42 即成功。内含**策略 1先写码后向调试器申请授权和策略 2调试器分配 RX vm_remap建 RW 别名**两套方案自动回退jit_wx_probe()一组 W^X 对比探针用来判断写权限被内核悄悄剥离这类问题的根因到底在虚拟机还是硬件上。六、它在 Madeira 整体架构中的位置 理解了这个模块再看 StikJITHelper.swift 中的生产路径就顺理成章了iOS 26 上执行 JIT 代码需要调试器StikDebug授权。应用通过jit26_prepare_region()/jit26_detach()BRK 断点协议L403-L423与调试器通信让调试器认证RX 页内容没有调试器时jit_install_trap_handler()装一个 SIGTRAP 处理器跳过 BRK 指令应用不至于崩溃最终FEX 生成的全部 x86→ARM64 翻译代码都落进这块双映射池——正如 docs/WOW64.md 中所述Everything that executes comes from the one dual-mapped RX/RW JIT pool。七、相关文件索引文件内容app/Madeira/JITAllocator.h双映射区域的公开 API 与早期虚拟地址占位变量声明app/Madeira/JITAllocator.c核心实现jit_region_create、no-footprint 豁免、W^X 探针、iOS 26 BRK 协议app/Madeira/StikJITHelper.swift生产级 JIT 池调试器分配 vm_remap别名 Jetsam 豁免docs/WOW64.md32 位游戏如何复用同一块双映射 JIT 池总结JITAllocator 的双映射技巧可以用一句话概括用mach_make_memory_entry_64拿到一块可 RWX的命名内存凭证再用两次vm_map在同一物理页上开出 RW 和 RX 两扇窗。写码走 RW 窗、执行走 RX 窗sys_icache_invalidate保证缓存一致mach_memory_entry_ownership让内存逃过 Jetsam 的眼线。这套组合拳既是 iOS 平台上做 JIT 的经典范式也展示了面对操作系统约束时理解内核、巧用 API的解决思路。【免费下载链接】MadeiraRun x86-64 Windows PC games on jailed iOS via FEX-Emu Wine DXMT项目地址: https://gitcode.com/GitHub_Trending/mad/Madeira创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表