ARTICLE DETAIL

资讯详情

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

Madeira Wine 分支的 Mach 上下文捕获与线程挂起:实现原理与调试完整指南

Madeira Wine 分支的 Mach 上下文捕获与线程挂起:实现原理与调试完整指南 Madeira Wine 分支的 Mach 上下文捕获与线程挂起实现原理与调试完整指南【免费下载链接】MadeiraRun x86-64 Windows PC games on jailed iOS via FEX-Emu Wine DXMT项目地址: https://gitcode.com/GitHub_Trending/mad/Madeira在Madeira把 x86-64 Windows PC 游戏跑在 iPhone 上的开源项目中Wine 分支用原生Mach 上下文捕获重新实现了线程挂起SuspendThread机制绕开了 iOS 上 POSIX 信号挂起失效的死胡同。本文讲清楚它的实现思路、关键参数与调试方法帮助你在游戏卡死、Steam 启动挂起等问题面前快速定位。一、背景iOS 上信号挂起为什么是死的Wine 上游的线程挂起依赖 POSIX 信号向目标线程发SIGUSR1由目标线程自己暂停并填好上下文。但 Madeira 的 mach_ios.c 注释里记录了关键事实在 iOS 上__pthread_kill返回成功但usr1_handler永远不会执行结果就是SuspendThread GetThreadContext永久挂起Steam 的看门狗直接把启动卡死。因此 Madeira 换了思路由 wineserver与游戏同进程、同 Mach task 的线程直接通过 Mach 原语去抓目标线程的上下文而不是等目标线程配合。二、Mach 上下文捕获的四步流程核心函数是ios_fill_thread_context()位于 build/wineserver/mach_ios.c#L876-L1081流程可以概括为四步挂起thread_suspend(port)让目标线程短暂停住保证快照一致抓原生 ARM64 寄存器thread_get_state(ARM_THREAD_STATE64)拿到 pc、sp、cpsr、x0–x28、fp、lr抓客用 x86-64 寄存器同一 task 内用mach_vm_read_overwrite从TEB → ChpeV2CpuAreaInfo → ContextAmd64偏移 0x1788 / 0x18读出 AMD64 CONTEXT。这里的RIP是 FEX 最近一次块边界同步的值正好是 Steam 卡死检测看门狗采样的地址——线程在前进RIP 就在前进恢复thread_resume(port)立刻放行普通快照只做瞬间停顿不产生持久挂起也就没有锁持有者死锁窗口。所有结构体字段偏移如IOS_A64_RIP 0xf8、IOS_TEB_CHPE_CPUAREA_OFF 0x1788都以宏定义在 mach_ios.c#L429-L475调试时可逐一对应。三、线程停在系统调用里ml716 的syscall 帧替换一个隐蔽的坑线程阻塞在libsystem_kernel __ulock_wait2等系统调用里时thread_get_state抓到的是普通的 Mach-O ARM64 地址。ARM64EC 的NtGetContextThread封装会把该Pc直接映射成Rip交给调用方等于把 Mach-O 地址冒充 x64 RIP——Mono 于是无法分类上下文挂起—取上下文—恢复—再挂起无限重试还一直攥着目标线程需要的临界区。实测案例代码注释 ml716在Marvel Cosmic Invasion上每次重试都返回rip0x23ddd1ae8且is_ec0。修法在 mach_ios.c#L909-L977用与is_inside_syscall()完全一致的判定kernel_stack sp syscall_frameTEB0x378 / TEB0x3a8命中后用保存的syscall framestruct syscall_framex[29] 在 0x000fp/lr/sp/pc/cpsr 依次在 0xe8–0x108共 0x330 字节替换刚抓到的寄存器视图该特性由环境变量MADEIRA_CTX_FRAME1选择性开启任何校验失败都回退旧行为绝不臆造状态。四、真·线程挂起ml730 的持久 Hold 与开关上面流程里线程挂起后马上恢复Wine 的挂起契约却要求挂起 停止。Mono 的混合挂起依赖这个契约它会标记线程为STATE_BLOCKING_ASYNC_SUSPENDED若线程继续跑并离开阻塞区mono_threads_transition_done_blocking()会报错进入eb fe死循环空转——这就是观察到的墙面。ml730 引入可选的真挂起mach_ios.c#L487-L538核心是显式的物理 hold 记账场景行为普通上下文快照suspend → capture → resume原样首次逻辑挂起suspend → capture →保留 hold不 resume嵌套逻辑挂起只加计数器不二次 Mach suspend最后一次逻辑恢复恰好释放一个 Mach hold实现上是ios_thread_mach_hold()与ios_thread_mach_release()mach_ios.c#L560-L612配合ios_pending_persistent_hold让首次挂起直接继承捕获时已有的停住状态。为什么默认关闭因为 Madeira 里 wineserver 与游戏共享同一个 Mach 进程、甚至同一个分配器如果冻住的是持有 malloc 锁或 FEXCodeInvalidationMutex的线程挂起方自己也会死锁。Windows 下挂起方不共享堆所以没事这里共享所以用MADEIRA_REAL_SUSPEND1显式开启也可在madeira.cfg的real-suspend选项配置见 ContentView.swift#L2601-L2602 与 ConfigCatalog.generated.swift#L294。值得学习的一个防御细节ml730bmach_ios.c#L540-L557hold 标志只允许 0/1。未初始化的内存被mem_alloc()用0x55填充后会读出0x55555555恰好像已挂起会让 hold 全部静默变 no-op、release 去恢复根本没停过的线程。代码因此拒绝处理无法识别的值并大声报错而不是猜测。五、调试清单日志标签速查三个开关对应的日志前缀都打到 stderr是排查的主要抓手日志标签来源看什么[real-susp]ml730ops/holds/releases/outstanding聚合统计每 128 次操作一行 tickHOLD FAILED / RELEASE FAILED带kr码CORRUPT HOLD FLAG说明标志位损坏[ctx-frame]ml716每次用 syscall frame 替换时的mach_pc / sp / frame / frame_pc / frame_sp前 48 条[srv-getctx]ml715同一次捕获中原生 ARM64 视图与保存的 AMD64 视图并排输出按tid/teb native pc与 ntdll 侧的[ec-getctx]记录关联注意两个计数器在不同模块不要按行序对齐调试建议顺序先开MADEIRA_CTX_FRAME1观察[ctx-frame]是否命中inside_syscall1对比[srv-getctx]中 native 与 saved_amd64 两侧 RIP确认调用方拿到的是哪一侧只有怀疑线程挂起后还在跑导致 Mono 崩溃时才开MADEIRA_REAL_SUSPEND1并盯[real-susp]的outstanding是否归零。六、相关文件与延伸阅读主实现build/wineserver/mach_ios.c捕获、hold、WoW64 上下文mach_vm 类型垫片build/wineserver/mach_ios_shim.hiOS Mach 头垫片build/ntdll-unix/shims/mach/mach_vm.h32 位游戏WoW64机制docs/WOW64.md构建全流程docs/BUILDING.md小结Madeira 的 Wine 分支把信号挂起替换为服务端 Mach 快照 可选持久 hold用 syscall 帧替换解决系统调用阻塞时的视图错乱再用显式 hold 记账与强校验标志位堵住静默失败。三个环境变量MADEIRA_CTX_FRAME、MADEIRA_REAL_SUSPEND配合[ctx-frame]、[srv-getctx]、[real-susp]日志构成了一套可开关、可回退、可观测的完整调试路径。【免费下载链接】MadeiraRun x86-64 Windows PC games on jailed iOS via FEX-Emu Wine DXMT项目地址: https://gitcode.com/GitHub_Trending/mad/Madeira创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表