ARTICLE DETAIL

资讯详情

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

为什么 Madeira 的 JIT 池必须提前预留:调试器分离后无法新建可执行映射的完整解析

为什么 Madeira 的 JIT 池必须提前预留:调试器分离后无法新建可执行映射的完整解析 为什么 Madeira 的 JIT 池必须提前预留调试器分离后无法新建可执行映射的完整解析【免费下载链接】MadeiraRun x86-64 Windows PC games on jailed iOS via FEX-Emu Wine DXMT项目地址: https://gitcode.com/GitHub_Trending/mad/MadeiraMadeira 是运行在 iPhone 上的 Windows 游戏模拟器靠 FEX-Emu 实时把 x86-64 指令翻译成 ARM64 代码执行。由于 iOS 26 只允许在调试器挂载期间创建可执行内存Madeira 必须在调试器分离前就把 JIT 池预留并分配好——一旦分离进程生命周期内都无法新建可执行映射。本文将带你完整看懂这套提前预留机制的设计动机与实现细节。一、先搞懂iPhone 上的 Windows 游戏是怎么跑起来的Madeira 在单个 iOS 应用内叠了三层技术层作用FEX-Emu运行时把游戏的 x86 / x86-64 代码翻译成 ARM64这就是JITWine 11.4提供 Windows 运行环境以 ARM64EC 原生运行DXMT / madeira-d3d12用 Metal 渲染 D3D 9/10/11/12 图形关键点在 FEX-Emu它是JIT即时编译模拟器——游戏每运行一块 x86-64 代码就在内存里现场写一段 ARM64 机器码并立即执行。这就意味着进程需要可执行内存而这正是 iOS 防破解机制盯得最紧的东西。二、iOS 26 的铁律JIT 只在调试器挂载时合法苹果从系统层面禁止普通 App 创建可执行映射iOS 26 的 TXM信任执行模块把这条路封得很死mprotect(PROT_EXEC)会被直接拦截即使返回值成功X 权限也被悄悄剥掉唯一的合法通道是调试器分配的页面且只能在它原来的虚拟地址上执行App 通过csops查询CS_DEBUGGED标志判断调试器是否挂载。所以 Madeira 的启动流程必须借助外部调试器 StikDebugApp 通过 URL scheme 唤起它内置脚本 madeira-jit.js 挂载到本进程双方用BRK #0xf00d指令约定了一套协议——App 发 BRK 指令调试器拦截后替它分配 RX可执行内存页。核心发起逻辑在 StikJITHelper.swift。⚠️ 但调试器不能一直挂着StikDebug 有约 48 秒 CPU/60 秒的预算跑满约 52 秒就会强制退出——真在游戏里被它甩掉整个会话就卡死。所以 Madeira 必须在自己选定的时机主动分离调试器。三、分离后为什么不能再建可执行映射这是理解提前预留的关键。virtual_ios.c 里有一段注释说得很直白TXM only allows execution from debugger-allocated pages at theiroriginal virtual addresses. Remapping those pages elsewhere doesnt preserve execute permission.翻译过来就是三条死规定mprotect(PROT_EXEC)被 TXM 拦截——App 自己永远无法把普通内存变成可执行内存执行权限绑定调试器分配 原地址——把 RX 页 remap 到别处权限随之失效调试器一分离这条唯一的授权通道就永久关闭——此后进程无法再新建任何可执行映射。结论很残酷整个 Wine FEX 会话能用的可执行内存必须全部在调试器还在岗时一次性拿到手。这就是 JIT 池JIT Pool存在的唯一理由一块几百 MB 的大池子之后所有 PE 代码段拷贝进来、所有 FEX 翻译代码写进去全部在池内执行。四、JIT 池如何提前预留三步锁定地址空间预留不只是早分配而是在三层时间点上抢占地址空间谁先到谁得池。第一步镜像加载瞬间就用占位映射圈地iOS 的进程里有一段约 1.4GB 的低地址空隙池子必须落在其中。但等你用户点击启动时App 自己的运行时分配早已把空隙切成碎片。JITAllocator.c 末尾的构造函数在main之前就执行先以PROT_NONE占用0x140000000起 128MB 的可执行窗口——因为RDR2.exe这类无重定位目录的程序必须加载在这个固定基址占不到它游戏必挂再占用窗口上方最大的一段连续空间作为池占位。PROT_NONE只占地址不占物理内存零成本。这一步是后来ml1040版本补的课实测同一款游戏三次启动空隙被切成 628MB / 604MB / 349MB 三档池子大小随之在 608MB 到 448MB 之间抖动——448MB 那次光镜像拷贝就吃光了池子shell32从池外地址执行进程直接死亡。第二步调试器分配 RX 池并反复校验落点StikJITHelper.swift 的allocatePool()在调试器在线时执行流程像一场地址空间拍卖钉桩抬高分配水位FEX 的指令发射在池子低于0x119000000时有 bugdispatcher 字面量修复错位分支飞进零内存所以先塞 16MB 的钉桩块把下一次分配的落点顶过这条下限普查空洞扫描0x119000000到 64GB 之间所有 ≥64MB 的空洞如果最大空洞装不下默认池就自动缩小池子去适配——宁可池小一点也不能落到禁区请求调试器分配jit26_prepare_regionBRK 协议让调试器分配 RX 页再vm_remap出同一物理页的 RW 别名供 FEX 写入代码落点三查低于下限吃掉0x140000000可执行窗口落进客用 64GB 窗口[0x70G, 0x80G)Wine 的 PE 镜像区故障处理器会把池代码误判为 guest 地址三者任一命中就作废重掷最多 3 次仍失败则拒绝启动并提示重启——因为池子落点是内核按当时的内存布局决定的新进程通常能换个地方。第三步尽早主动分离调试器池子终身有效ContentView.swift 里的启动编排池子就绪后先early detach等游戏出第一帧再稳定 20 秒后二次确认分离。分离之后池内已预授权的 RX 页继续可执行双映射 RW/RX 保持有效FEX 的后续编译走trap 模式写 RW 别名、在预授权的 RX 地址执行不再依赖调试器但任何新的可执行映射请求都会失败——这就是分离后无法新建可执行映射的完整含义。五、提前预留还顺手解决了两个隐形杀手 预留机制不只保地址还保内存配额Jetsam 内存上限池子写满几百 MB 后会撞上 iOS 的物理内存记账上限。JITAllocator.c 的jit_make_region_no_footprint()通过私有 Mach API 把池子标记为NO_FOOTPRINT让它不计入 Jetsam 配额——曾有 848MB 池子写入后直接被系统Terminated due to memory issue池耗尽的连锁崩溃池不够大时 FEX 代码缓存会反复翻滚每次约 1 秒冻结极端情况下代码溢出到池外地址执行进程无日志死亡。提前圈地 空洞普查保证池子拿到的是最大可用空洞而不是随机碎片。六、关键文件导航 文件职责JITAllocator.c双映射池创建、PROT_NONE提前圈地、Jetsam 豁免JITAllocator.hBRK 协议接口jit26_prepare_region/jit26_detachStikJITHelper.swift唤起调试器、钉桩/空洞普查/落点校验、分配池子、分离madeira-jit.js调试器侧脚本拦截 BRK、代 App 分配 RX 内存ContentView.swift启动编排池子就绪 → 分离 → 起 Winevirtual_ios.cPE 代码段拷贝进池、TXM 执行限制的处理signal_arm64_ios.c分离后的 fault 兜底与 trap 模式 JIT 支撑总结一句话回顾这条设计链TXM 只认调试器分配的原地址 → 可执行内存的唯一合法来源是调试器 → 调试器必须提前分离它自己会超时掉线→ 所以全部可执行内存必须在分离前预留并分配完毕 → 而预留又必须提前到镜像加载瞬间因为低地址空隙会被运行时分配不断切碎。这正是 Madeira 在 iPhone 上跑动完整 Windows 游戏所付出的底层代价用启动期一场精密的地址空间博弈换会话期的执行自由。【免费下载链接】MadeiraRun x86-64 Windows PC games on jailed iOS via FEX-Emu Wine DXMT项目地址: https://gitcode.com/GitHub_Trending/mad/Madeira创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表