资讯详情

Madeira的TSD/TLS槽位之战:ARM64EC模块为何不能再硬编码TSD

📅 2026/10/4 4:05:13 | 华诺云谱 👁 阅读
Madeira的TSD/TLS槽位之战:ARM64EC模块为何不能再硬编码TSD
Madeira的TSD/TLS槽位之战ARM64EC模块为何不能再硬编码TSD【免费下载链接】MadeiraRun x86-64 Windows PC games on jailed iOS via FEX-Emu Wine DXMT项目地址: https://gitcode.com/GitHub_Trending/mad/MadeiraMadeira是一个让越狱 iOS 设备运行 x86-64 Windows PC 游戏的项目技术路线为 FEX-Emux86-64 模拟器 WineWindows API 层 DXMTDirectX 转 Metal。而在整个栈最底层有一场无声的槽位之战Wine 的线程环境块TEB存放在 pthread 的 TLS 线程局部存储槽中而 ARM64EC 模块 FEX 曾硬编码了这个槽位——一次 Apple 框架的抢位让所有游戏进程集体阵亡。本文讲清楚 Madeira 是如何把硬编码 TSD 换成自发现 运行时补丁的。一、背景每个线程的信箱以及谁拥有第 275 号在 Apple Silicon 上线程局部存储TLS的物理形态是一条原始 TSD 数组基址放在TPIDRRO_EL0寄存器里第 N 号槽位就在基址 N×8 处。可以把它想象成每个线程都有一排信箱各组件往自己的信箱里放东西。Madeira 里有两方要用信箱Wine把每个线程的 TEB线程环境块Windows 线程的根指针放进一个 pthread key 对应的槽里FEXARM64EC 模块模拟器在 ARM64EC 混合环境里运行 x86 代码需要从 TLS 里读 TEB 才能切换上下文。旧的 FEX 汇编里直接写死了ldr xN, [xN, #0x898]——即第 275 号槽偏移 0x898。问题在于275 号是一个动态 pthread key谁先调用pthread_key_create()谁就拿到它而且赢家因设备而异、因加载的框架而异。Wine 根本不拥有这个槽。二、灾难现场M4 iPad 上的抢位真实事故完整记录在 thread_ios.c 的注释里一台 M4 iPad 上首次nextDrawable唤醒的Metal 图形栈抢先拿到了 275 号 key它把槽位重置为 NULL此后所有 FEX 的 x18 跳板从该槽读 TEB读到的是TEB 0进程死在读TEB-PEB的那一瞬间。更隐蔽的是另一面275 号 key 若带着ObjC 析构函数被 Apple 框架分配线程退出时 pthread 的清理逻辑会拿 TEB 指针当对象调用objc_release——非对象被释放winhttp 的解析线程一退出就崩。一个看起来能用的魔数变成了随设备漂移的地雷。这就是硬编码 TSD 槽位的原罪你写的地址不属于你。三、新打法哨兵自发现发现失败就自杀Madeira 在 loader_ios.c 中重建了整套信任链分四步1. 哨兵探测真实槽位启动早期任何 PE 模块加载之前Wine 创建自己的 pthread key写入一个唯一魔数0x5445425F534C4F54即字符串 TEB_SLOT然后扫描 TSD 数组的 512 个槽看哨兵落在哪里——那个位置就是本进程里真正属于该 key 的原始槽。为什么不用扫描 TEB 值因为 TEB 本身能通过 Wine 自己的teb_key找到扫 TEB 会误匹配到别人拥有的槽。哨兵值只属于这次探测不会撞车。2. 校验 绝不降级找到槽后必须验证槽里现在装的确实是 TEB且基址与跳板将来使用的一致。验证失败直接abort()——源码注释写得很直白错误的偏移不是降级模式而是每个被补丁模块里必定发生的 TEB0 故障。宁可当场死不要无声地错。3. 通过 ntdll 导出表发布唯一权威值发现完成后load_ntdll_functions 把偏移写进 ntdll 的导出变量ios_teb_tsd_offset。ARM64EC 模块FEX导入这同一个值而不是各自硬编码——全系统从此只有一个事实来源。4. 二进制补丁器执行前改写汇编FEX 模块里那三句手写的汇编mrs xN, TPIDRRO_EL0 ; 取 TSD 基址 and xN, xN, #~7 ; 对齐 ldr xN, [xN, #slot*8] ; 读槽 —— 槽号是占位符它的槽号在汇编期不可能知道。于是 virtual_ios.c 的 Pass 0 在镜像还是 RW 别名、尚未执行时精确匹配这条三指令序列三条指令寄存器必须一致顺带的 LDR 撞不上把第三条的立即数重写为已发现的槽偏移。两个防坑设计值得学习字面池保护先用数据地图排除 literal pool 中的假指令避免把数据当指令打补丁FATAL-BY-SILENCE 哨兵如果某镜像在槽位尚未发现时就被打补丁占位符会原样存活后续所有 TEB 读取都变成读别人的数据且不会触发任何故障——所以此路径会被显式大声报告而不是保持安静。四、线程退出用自己的钥匙清自己的槽灾难解决后收尾也必须对称。线程退出时不再去捅 275 号裸槽而是通过 Wine 自己创建、自己持有的 key 把槽置 NULLthread_ios.c槽归谁管谁负责清理。同理server_ios.c 的线程采样器也不信任 libpthread 私有头文件里写死的偏移 224而是用写哨兵 → 扫描的方式自我标定TSD 数组在struct pthread里的真实位置。五、给新手的三条经验不要硬编码你不拥有的东西。动态 key、私有结构布局、魔数偏移都是会随设备和系统版本漂移的暗雷自发现优于信任假设写入哨兵、扫描、校验、发布单一权威值——这套流程在 loader_ios.c 里就是一个可复用的模板让失败大声。最危险的不是崩溃而是占位符存活 读到别人的数据 毫无故障信号。把静默路径变成显式 FATAL是嵌入式/系统层开发的老练标志。总结Madeira 的这场 TSD/TLS 槽位之战本质是一次从魔法数字到运行时事实的架构升级Wine 启动时用哨兵自发现真实 TLS 槽经 ntdll 导出表发布唯一权威偏移二进制补丁器在 FEX 镜像执行前重写其占位汇编。ARM64EC 模块因此不再依赖任何固定槽位——这也是 Madeira 能在不同越狱 iOS 设备上稳定跑起 Windows 游戏的底层基石之一。想深入了解构建与运行细节可参阅项目文档 BUILDING.md 与 WOW64.md。【免费下载链接】MadeiraRun x86-64 Windows PC games on jailed iOS via FEX-Emu Wine DXMT项目地址: https://gitcode.com/GitHub_Trending/mad/Madeira创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑