XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
Readme.md

Native GC、异常处理与单核线程补丁

仅修补项目 obj/.../cosmos/ref/Cosmos.Kernel.Core.dll 私有副本,不修改 NuGet 缓存。锁定 Cosmos 3.0.94 的 PreciseScanFrameChain 原始 IL(149 字节、75 指令、256 帧上限),未知形态拒绝修改。

两个原始出口会在未扫描完栈时直接返回:256 帧上限耗尽、CFI 展开后 IP/SP 无效。补丁在两处调用有界 helper,仅当最后可信 prevSp != 0 && prevSp < threadStackEnd 才执行现有 ScanMemoryRange(prevSp, threadStackEnd),并重定向原先直达 invalid 返回指令的分支。原精确扫描、GCDesc、标记栈溢出恢复及回收逻辑均保留。不是屏蔽非法引用的 TypeCast 防护。

dotnet run --project Tools/GcRuntimePatch/GcRuntimePatch.csproj -c Release --artifacts-path C:/Absolute/Build/GcPatch -- C:/Absolute/Kernel/obj/Release/net10.0/linux-x64/cosmos/ref/Cosmos.Kernel.Core.dll

在 RunPatcher 之后、WriteIlcRsp 之前运行。GC 与 EH 的全部形态检查通过后才一次原子替换私有 DLL;未知 EH 形态不会留下已改 GC 的半成品。工具保存旁侧 .XfeGc.json,记录稳定的官方原始 SHA256、当次输入/结果 SHA256、适用版本与补丁范围;重复调用验证 receipt 和已有结构,保持 DLL 字节不变。Cosmos patcher 再次恢复为精确官方原始文件时,可以忽略旧 receipt 并重新生成补丁。原始固定 SHA256 为 5d63cc0687931c69f0cb24740f02172e86b9e7664074d2f7b08fcf1a405c062e。

EH 捕获链上限

Cosmos 3.0.94 的三个 s_activeCatch* 数组、元数据常量和捕获深度比较原本均为 64。超过上限时只打印警告,仍调用未登记的 catch;从该 catch 再次抛出时,无法跳过当前 funclet,可能重复进入同一处理器。Jint 私有 finally 清理补丁增加了真实 catch/rethrow,深层解释器调用会触及这个边界。

本工具同步将三个数组与比较改为 4096,与该运行时栈展开最大帧数一致(约增加 96 KiB 数组空间;线程隔离后每个线程分别持有)。达到新上限时明确 FailFast,禁止继续执行未登记 catch;捕获剪枝、帧展开及 catch 传递方法均保持原 IL。提高登记上限不保证任意深度线程栈足够。

处理器已经选定后,allocation-free helper 最多输出 8 种不同 EEType 的第一条异常记录;同类型重抛不重复输出。不访问异常消息、反射或格式化分配。输出 XFE_EH_FIRST_TYPE|EEType|0x…|Depth|…|Handler|0x…|Category|…,类别为 Timeout/OutOfMemory/InvalidCast/StackOverflow 的完整异常类名或 Other,其余类型可以用 ELF 符号映射 EEType。此处仍处于原异常重入保护内;局部变量明确初始化。receipt 记录 EhCatchTrackingLimit=4096、EhOverflowFailFast=true 与 trace 上限。

Funclet 的根 EH 表

固定运行时的 ExceptionHelper.TryGetMethodLSDA 原样接收 funclet 的 LSDA 与代码起点,而其 EH 解析仅跳过 funclet 重定向字段,未跟随主方法的 EH 表。在仍然活动的 catch 中再次 try/catch/rethrow 时,可因此漏掉内部处理器。FuncletEhRuntimePatch 仅修正 ExceptionHelper 的私有包装方法:按原 GCInfo 解码器使用的有符号 int32 相对偏移,取得主方法 LSDA,并将方法起点还原为主方法起点。后续 code offset 与 handler 地址在同一主方法地址空间计算。共享 MethodGcInfoLookup、GC 元数据查询和 CFI 展开契约不变;未知包装方法形态拒绝修改。

单核线程的 EH 状态与后台工作

Cosmos 3.0.94 已为调度线程保存寄存器、栈、TLAB 与托管 ThreadStatic,并扫描注册线程的堆栈,但 EH 的 native ExInfo 链及七个托管字段原本全局共享。本补丁在唯一的 PerCpuState.CurrentThread setter 上保存/恢复这八项状态,包含活动 catch、重入保护和异常诊断数组。各线程的四个数组在构造期间分配,调度切换 helper 不分配、不开锁、不抛异常。初始 null→boot idle 切换接收当前启动链,不能把仍然活动的启动 EH 状态清空。原 native throw/pop/rethrow 路径保留,新增两个叶函数仅访问同一 ExInfo 指针。

这些字段保存在注册的 SchedulerThread 实例中,沿正常托管 GC 根保留。此实现只支持已启动调度器的 单 CPU;导出的版本查询对其他配置返回 0。NativeThreadRegression 在真实内核中启动两个各 8 MiB 栈的线程,要求不同非零调度 ID,catch 内逐轮握手,在各自 50 ms 无 yield 忙循环期间确认另一线程实际前进;同时验证 32 次重抛的异常身份、4 次 GC 后的栈上对象及 32 次抢占检查。另检查 ThreadStatic 整数、对象根和 CoreLib Thread.CurrentThread/ManagedThreadId 各自隔离,普通线程不能变成 ThreadPool 线程。20 秒超时或任何失败都使 Kernel 明确 fail-closed 并停止桌面启动,不留下后台测试线程后退回同步解释器。

原生验收 Proof-1791985248864966900 已验证 Steps=32、Collections=4、PreemptionChecks=32,线程 ID 为 2/3。启动后普通 UI 线程与专用编辑器 worker 分别拥有 8 MiB 栈,避免 CPU 密集工作使 boot idle 上的 UI 饥饿。worker 使用 16 槽、单生产者/单消费者有界队列;计数器按 uint 环绕且槽数为 2 的幂,执行应用工作时不持有队列锁。编辑器的独立浏览器、DOM、Jint 与字体画布始终归 worker,UI 仅发布有界输入并绘制不可变快照。每窗口最多一个工作请求,只由 UI 排队;worker 有限地处理输入、执行一次 pump、发布快照后返回,禁止永久循环或从 worker 自行加入 SPSC 队列。关闭窗口仅丢弃结果,不异步终止 Jint。

编辑器预处理使用与桌面相同的单调时间。初始显示使用第二个独立 delivery 画布预热可见文本在实际屏幕缩放下的 glyph;这个画布发布后由 UI 独占接收,不共享仍然活动的 worker 字体对象。只有同内置字体实现、同缩放率的正常屏幕画布可接收有界字体/测量/覆盖缓存。不同目标、缩放不匹配及已转移后的 worker 预热均拒绝。缓存字典始终分别拥有,delivery 字体及覆盖对象只在 worker 完全停止访问该 delivery 画布后交给 UI;普通页面切换画布仍须重新布局。BrowserPaintSnapshot 删除全部 DOM node 引用,深拷贝 decoration 列表与 canvas 像素,不携带原布局盒或 text-metrics provider,UI 绘制不回调引擎。宿主的像素一致性和引用隔离检查不能代替整个原生 actor 的长期内存与交互验收。

GC 仍会关中断并暂停整个单核系统,未实现并发 GC。 此补丁允许解释器执行期间 UI 被定时器抢占,不保证分配/GC 或其他原有临界区零停顿,也不代表多核、任意共享托管库或跨进程隔离已经安全。必须分别验收实际 Monaco 初始化期间的 UI 输入进展;结构测试及上述线程自检不能代替应用层验收。

分配失败保护

固定运行时在 GC 重试分配失败后返回 null;RhNewString、RhpNewFast 与 Array helpers 会把它直接交给调用方,导致之后的 CPU 页错误。两个 guard 覆盖 GC.AllocObject 恢复中断后的唯一出口,以及 Runtime.Memory.AllocObject 在 GC 启用前的原始分配、清零前。普通与 pinned GC 分配共用第一个出口,成功时保持原指针不变。

失败只输出 XFE_RUNTIME_OOM|RequestedBytes|…|Action|GuestHalt 和明确 [FAILFAST] 常量,然后永久停止执行;无需异常对象、反射、格式化或任何新的分配,也不触发 ExceptionHelper 首次初始化分配追踪数组。OOM 会停止整个 guest,当前没有恢复或进程隔离保证。guard 不在持有 interrupt scope 的 slow allocator 内抛异常,避免 native finally 缺陷导致中断无法恢复。receipt 明确记录此边界。

调度线程栈另外使用 raw MemoryOp.Alloc,不经过上述托管对象 guard。ThreadStackRuntimePatch 在固定 InitializeStack 的 raw 分配之后、存入 StackBase/调用 ThreadContext.Initialize 之前检查返回值;空指针输出 XFE_RUNTIME_STACK_OOM、请求字节数、raw 物理页剩余/总量并停止 guest。这防止缺页写入地址 0x138,不代表原始堆耗尽或碎片问题已修复。NativeRuntimeMemoryDiagnostics 导出原 PageAllocator 的只读页计数,诊断时不能将 GC 的 freed-object 数量当成 raw 可用内存。Cosmos 的 TrueTypeFont 与当前字体画布使用托管对象和数组,没有独立 native font malloc/free 生命周期。

raw 栈分配首次失败时先执行一次真实 GC,再重试同尺寸的原 allocator;只有重试仍失败才停止。固定 GC 原先只在托管 TLAB 无法 refill 后触发收集,raw 栈在 GC 周期低谷失败时可能仍有大量可回收的空 segment,此重试不会提高 RAM 配置或隐藏最终失败。OOM 还输出 RawLargestFreeRunPages;只读扫描在保存/屏蔽中断期间遍历固定 RAT,完成后恢复原 flags,周期查询使用同一实现,以区分总空闲与连续空闲不足。

一次实际取消第二个编辑器后,失败请求 9868860 精确等于 CJK Regular 字体资源 9868836 加数组头 24 字节;此前 raw 总空闲仍超过 300 MiB。CanvasAdapter 因而在早期统一读取不可变字体资源字节,后续画布共用这些只读输入,各自重新创建独立 TrueTypeFont、metrics 和 glyph/raster 字典。已逐行核查固定 LunarFonts 对 _data 只有读取,宿主真实字体回归同时验证输入 SHA256 不变、byte[] 身份相同、可变 TrueTypeFont 身份不同及全部渲染像素一致。此修复消除重复 9.4 MiB 连续数组请求,原生长期运行仍需单独验收,不能仅据宿主通过宣称所有碎片已解决。

此保护不降低正常验收:512 MiB 下 Monaco 绘制与输入仍须通过。独立受控页面可以请求 new ArrayBuffer(1073741824) 验证失败确实进入 guard;宿主结构验证不能代替该原生负向验证。负数组长度、32 位分配尺寸溢出等独立契约不在本补丁的覆盖声明中。

Fixture/NativeGcRegression.cs 需显式编译进诊断内核。调用 NativeGcRegression.Run(),核对 FramesVerified == 321、CollectionsRequested == 2、FailureDepth == -1。每层真实 NoInlining | NoOptimization 调用持有独立闭包和对象图,最深层两次 GC.Collect(),返回逐层检查数值、字符串和父对象字段。下层对象不引用上层对象,避免一个深层根意外保留全部父帧。CoreCLR 上通过仅用于检查测试控制程序,必须另记真实 Cosmos/QEMU 结果。

同一 fixture 文件还提供 NativeEhRegression.Run()。它在真实 NoInlining | NoOptimization 递归中产生 81 层 catch/rethrow,超过原先 64 条登记容量,核对 FramesCaught == 81、原异常身份保持、随后四次独立 throw/catch 可正常恢复。采用 81 层是因为固定 Cosmos 的主线程 Limine 栈请求仅 1 MiB,而调度线程默认 256 KiB;321 层重抛可能先耗尽栈,无法单独验收追踪缺陷。GC fixture 的 321 层保留。

Tests/GcRuntimePatch.Check.csproj 为宿主结构与控制回归:私有路径/版本/IL、两个 GC 出口/分支、可信栈边界、其余 GC 方法不变、EH 三数组/常量/比较一致、overflow 必须终止、捕获剪枝与传递 IL 不变、trace 初始化/边界/隐私、字节幂等、稳定原始来源、正常重建接受旧 receipt、损坏输入拒绝且无部分写入、原包未改及两个 fixture 的 CoreCLR 对照。EH 宿主控制在独立 16 MiB 栈线程执行;原生 fixture 仍在真实主线程栈执行。输出明确 NativeGuestTested=false。

4K/512 MiB 首次 Monaco pump 失败 Proof-1791954595129723200 出现大量旧 64 条 EH 上限警告,没有 pump 结束;同 ELF 的 4K/1024 MiB 对照能够创建模型。修正捕获登记边界可避免无界处理器重入,并让首个异常类型可诊断,尚不能证明或宣称 512 MiB 的内存压力根因已修复。必须另验收实际 guest 的 GC 321 层、EH 81 层及 Monaco pump/绘制/输入。

原生 Monaco 首次失败记录 build/CSharpBoot/Proof-1791952508346144200:512 MiB 配置中两次 GC 后发生非法环境引用 CPU 页错误。相同 ELF 的 2048 MiB 对照 Proof-1791952763395507600 可导航并输出真实 Monaco 模型。这提示内存压力/GC 路径,但没有证明 256 帧截断是本次故障根因。原 DLL 已实际含 NoteMarkOverflow → ProcessMarkOverflow 恢复逻辑,不能仅凭标记栈满警告断言漏标。运行时最终分配失败返回 null 的 OOM 契约也另需核查。

当前补丁只补有界栈尾,不能替代对最后可信 CFI 寄存器保存位置的完整保守扫描;该位置可能位于 prevSp 以下。若原生控制仍失败,应继续记录准确展开/分配原因,而不是宣称已修复全部 GC。

调度回调的周期日志精确跳过,并将 StrideScheduler.OnTick 最后一个强制轮转比较单独改为 elapsedNs >= 1_000_000,使用明确的 1 ms 低延迟时间片。BootPlatformPatch 已将实际 LAPIC 周期设为 1 ms;原回调每次拿 1 ms 与 10 ms 比较,却没有累计时间片,因此原强制轮转路径永远不成立。新策略保留前面所有真实 elapsedNs 计账、10 ms Stride 权重分母、优先级比较、睡眠时钟、run queue 和 GC 路径。仅改日志入口与末尾 ldloc.2 两条指令;后者改为 64 位 1,000,000 常量,整方法增长 8 字节,所有分支目标、局部变量和其他 IL 由原始 SHA256 归一化指纹绑定。

GC receipt 通过实际 Core.dll 的 AfterSha256 绑定 SchedulerForcedTimesliceNanoseconds=1000000、SchedulerStrideAccountingDivisorNanoseconds=10000000 和精确尾部指令位置。两次构建字节幂等;宿主执行真实回调 IL,验证小于 1 ms 不强制轮转、1 ms 到达立即轮转、两可运行线程均取得进展,以及另一个线程虚拟 pass 大幅领先时不会让当前线程永远追赶。此为明确的调度策略调整,不是原调度语义完全不变的声明;宿主测试不能代替原生交互延迟、Sleep 墙钟或实体显示刷新率验收。

参考来源:项目 .tools/references/Cosmos-3.0.94 中固定提交的 GarbageCollector.PreciseStack.cs、GarbageCollector.Mark.cs、ExceptionHelper.cs 与 LimineStackSize.cs。Cosmos 源代码使用 BSD-3-Clause,原包不改变。