☰
Madeira 的 StikDebug 协议深度解析:BRK 0xf00d 命令分派完全解读
2026/10/4 21:40:57 网站建设 项目流程

Madeira 的 StikDebug 协议深度解析:BRK #0xf00d 命令分派完全解读

【免费下载链接】MadeiraRun x86-64 Windows PC games on jailed iOS via FEX-Emu + Wine + DXMT项目地址: https://gitcode.com/GitHub_Trending/mad/Madeira

本文带你完整解读Madeira的StikDebug 协议:它让 Windows PC 游戏(x86/x86-64)通过 FEX-Emu + Wine + DXMT 在未越狱的 iPhone 上运行,而协议的核心BRK #0xf00d指令正是 iOS 限制下"借调试器之手执行 JIT 代码"的命令分派桥梁——一条中断指令,就是一次系统调用。

一、为什么 iOS 上的 PC 模拟器要"借调试器的手"?

iOS 有严格的 W^X(写异或执行)内存策略:应用不能自己创建可执行内存,唯一例外是——当调试器附加、进程带上CS_DEBUGGED标志时,JIT 才被允许。Madeira 恰好重度依赖 JIT:FEX 动态翻译器要在运行时把游戏的 x86-64 指令实时编译成 ARM64 机器码。

于是 Madeira 设计了这套"握手协议":

  • 发送端:app 自身(JITAllocator.c里的内联汇编)
  • 应答端:StikDebug 调试器,或内置的 StikJIT 辅助进程,由一份捆绑的 JavaScript 脚本 madeira-jit.js 驱动
  • 信道:ARM64 的BRK #0xf00d中断指令

启用入口在 StikJITHelper.swift:通过stikdebug://enable-jitURL 把 bundle ID、当前 PID 和 base64 编码的脚本传给调试器,最多等待 90 秒,轮询直到CS_DEBUGGED置位且调试器真正在线。

二、协议总览:一条 BRK 指令 = 一次"系统调用"

🎯 协议约定非常简单——寄存器就是"接口":

寄存器用途
x16命令号(分派键)
x0参数 1(通常是地址;0 表示"帮我分配")
x1参数 2(通常是大小)
x0返回值(调试器写回的结果)

发送端在 JITAllocator.c 中只需两行汇编。以申请 JIT 内存为例:

__asm__ volatile( "mov x16, #1\n" "brk #0xf00d\n" : "+r"(x0) : "r"(x1) : "x16", "memory" );

执行到brk时,进程触发陷阱,控制权交给调试器。脚本读出寄存器、完成操作、把结果写回x0,再把 PC 前移 4 字节后继续运行——对游戏来说,这就是一次"透明的系统调用"。

三、x16 命令分派表:完整解读 🎯

madeira-jit.js 是协议的"内核态"。它只认两个 BRK 立即数:0xf00d(通用协议)和0x69(旧版协议),其余 BRK 一律跳过并把x0清零。

x16 = 0:CMD_DETACH 断开调试器

发送D命令让调试器脱附。注意CS_DEBUGGED是粘性标志——脱附后依然保持置位,JIT 能力不会失效,所以游戏加载 PE 依赖项完成后就可以断开,释放调试会话。

x16 = 1:CMD_PREPARE_REGION 准备可执行内存

这是最核心的命令,流程见 madeira-jit.js:

  1. 若x0 == 0(请求自动分配),脚本向 StikDebug 发_M<size>,rx,让调试器分配一块 RX(只读可执行)内存
  2. 调用prepare_memory_region(addr, size)把该区域标记为可执行
  3. 把最终地址写回x0,游戏端拿到的就是"开箱即用"的代码池基址

x16 = 3:CMD_MAP_PAGE_ZERO 在地址 0 放 TEB

Windows 的线程环境块(TEB)按惯例映射在地址 0,但 iOS 内核拒绝应用自己映射页 0。脚本借调试器的更高权限,从 app 内存读出 TEB 数据,直接写入地址 0(约 16KB),x0返回 0/1 表示成败。Wine 侧的发起点见 signal_arm64_ios.c。

x16 = 0x69:兼容旧协议

对旧版 StikDebug 的兼容路径,直接用x0作为地址与大小调用prepare_memory_region。

四、内存摆放:一次必须"掷"对骰子的分配 🎲

拿到调试器后,StikJITHelper.swift 的allocatePool()才是重头戏:它要申请一块默认 128MB 的 JIT 池(RX),再用vm_remap在其上叠一层 RW 别名视图——写代码走 RW,跑代码走 RX,这就是 W^X 下的"双映射"区域。

难点在于 iOS 内核第一匹配分配、不接受地址提示,池子落在哪里全凭运气。代码中用三个"红线区间"校验每次分配:

红线地址范围踩中后果
下限< 0x119000000FEX 的位置相关编码 bug 被触发,分派器跳进零内存
来宾 64G 窗口[0x7000000000, 0x8000000000)Wine 在此打包 PE 镜像,池代码会被误判为来宾地址,首帧黑屏
可执行窗口[0x140000000, +128MB)吞掉无重定位表游戏(如 RDR2.exe)的固定基址,TLS 指针全部失效

分配不合格就释放(或钉住)重试,最多 3 次;实在摆不下就宁可缩小池子甚至失败退出,也不让游戏带病运行——详细逻辑见 StikJITHelper.swift。池子就绪后还会调用jit_make_region_no_footprint()申请 jetsam 豁免,避免大块内存写入被系统按物理占用直接杀掉。

五、安全网:没人应答 BRK 时会发生什么 🛡️

协议最脆弱的一环是"BRK 发出去却没人接"。项目为每一种失败都准备了明确的退路:

  • app 侧:JITAllocator.c 安装 SIGTRAP 兜底处理器。没有调试器在线时,BRK 被处理器吞掉、x0置 0,应用得到"请求失败"的返回值,而不是崩溃
  • Wine 侧:信号处理器中的case 0xf00d专门识别游离的协议 BRK 并跳过(见 signal_arm64_ios.c),不会把协议中断当成崩溃上报
  • 脚本侧:只服务 BRK。其他真实故障(缺页、非法指令等)被还原为对应的 Unix 信号交还给 Wine 的sigaction处理器;同一故障 8 次无法投递时直接杀死进程——"可见的死亡好过一个永远挂起的线程"
  • UI 侧:ready状态要求CS_DEBUGGED置位且(调试器在线 或 本次运行的池已建成),防止"标志还在、调试器已走"的假就绪(见 StikJITHelper.swift)

六、关键文件速查表

文件角色
app/Madeira/JITAllocator.cBRK 发送端:jit26_prepare_region/jit26_detach
app/Madeira/JITAllocator.h协议 API 与 iOS 26 BRK 协议注释
app/Madeira/madeira-jit.js调试器侧主循环与 x16 命令分派
app/Madeira/StikJITHelper.swift启用入口(URL Scheme)与 JIT 池摆放策略
app/MadeiraJITHelper/MadeiraJITHelper.swift内置 StikJIT 辅助进程(XPC 转发 PID 与脚本)
build/ntdll-unix/signal_arm64_ios.cWine 侧 0xf00d 兜底与页 0 TEB 请求
docs/JIT.md官方 JIT 配置指南(StikDebug / 内置 StikJIT)

💡 延伸阅读:JIT 环境的完整配置流程(StikDebug 配对、LocalDevVPN、内置配对)见 docs/JIT.md,本文协议正是由其中的"Enable JIT"按钮触发。

七、一句话总结

BRK #0xf00d是 Madeira 在 iOS 铁幕上凿出的一个"合规通道":app 用寄存器当参数发起请求,调试器脚本按x16分派三个命令——分配可执行内存、修补页 0、脱附——再写回结果。配合精心设计的地址红线、重试策略和多层兜底,这套不足 400 行脚本的轻量协议,撑起了整个 Windows PC 游戏在 iPhone 上的实时动态翻译。

【免费下载链接】MadeiraRun x86-64 Windows PC games on jailed iOS via FEX-Emu + Wine + DXMT项目地址: https://gitcode.com/GitHub_Trending/mad/Madeira

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询