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

XFESystem

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

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/XFESystem

Fix single-CPU execution on multicore hardware and isolate network startup

b2a7235
XFE工作室室长 <mail@xfegzs.com>
提交于

代码差异

24 个文件 +1135 -18
Modified Source/XFESystem.Kernel/Kernel.cs +33 -9
@@ -40,24 +40,39 @@ public sealed class Kernel : Cosmos.Kernel.System.Kernel
40 40 private bool lastPointerReportLeft;
41 41 private NativeManagedWorkScheduler? editorWorker;
42 42 private System.Threading.Thread? uiThread;
43 private System.Threading.Thread? networkThread;
43 44 private const string ManagedDemoPath = "X:\\Programs\\ManagedHello\\ManagedHello.dll";
44 45
45 46 // Only the bounded serial byte write masks interrupts; formatting and all
46 47 // kernel/network work happen before entering the runtime's atomic log API.
47 48 private static void WriteDiagnostic(string message) => Log.WriteBytes(Encoding.UTF8.GetBytes("\n" + message));
48 49
49 protected override void BeforeRun()
50 public override void Start()
50 51 {
51 52 NativeBootProgress.Begin();
52 try { InitializeDesktop(); }
53 NativeBootProgress.Stage("A10", "Starting devices and drivers");
54 try { base.Start(); }
53 55 catch (Exception error) when (error is not OutOfMemoryException and not StackOverflowException)
54 56 {
55 57 NativeBootProgress.Failed(error.Message);
56 WriteDiagnostic("[FAILFAST] Desktop startup failed: " + error.Message + "\n");
58 WriteDiagnostic("[FAILFAST] Kernel lifecycle failed: " + error.Message + "\n");
57 59 while (true) Power.Halt();
58 60 }
59 61 }
60 62
63 protected override void OnBoot()
64 {
65 NativeBootProgress.Stage("A20", "Drivers completed; initializing console");
66 base.OnBoot();
67 NativeBootProgress.Stage("A21", "Console initialized");
68 }
69
70 protected override void BeforeRun()
71 {
72 NativeBootProgress.Resume("B00", "Managed desktop startup entered");
73 InitializeDesktop();
74 }
75
61 76 private void InitializeDesktop()
62 77 {
63 78 NativeBootProgress.Stage("B10", "Preparing input and packaged resources");
@@ -73,6 +88,8 @@ public sealed class Kernel : Cosmos.Kernel.System.Kernel
73 88 NativeKeyboardCapture.Prepare();
74 89 NativePointerCapture.Prepare();
75 90 NativeNvidiaDiscovery.Report(WriteDiagnostic);
91 WriteDiagnostic("XFE_CPU_TOPOLOGY|Physical|" + NativeBootProgress.PhysicalCpuCount() +
92 "|Scheduler|" + NativeBootProgress.SchedulerCpuCount() + "|StartedCpus|1|BootstrapLogicalCpu|0\n");
76 93 WriteDiagnostic("XFE_CSHARP_NATIVEAOT_BOOT_OK\n");
77 94 int documentTextResources = XFESystem.Documents.WebsiteAssets.PrewarmTextResources();
78 95 int browserTextResources = PackagedBrowserResources.PrewarmTextResources();
@@ -182,8 +199,8 @@ public sealed class Kernel : Cosmos.Kernel.System.Kernel
182 199 workspace.WriteBytes(ManagedDemoPath, bytes);
183 200 }
184 201 RunManagedApplicationCore(ManagedDemoPath, verifyDemonstration: true);
185 NativeBootProgress.Stage("B70", "Checking network (offline startup is supported)");
186 InitializeNetwork();
202 NativeBootProgress.Stage("B70", "Network starts in background after desktop");
203 WriteDiagnostic("XFE_NETWORK_DEFERRED|StartAfterDesktop|1\n");
187 204 WriteDiagnostic("XFE_BROWSER_CONSTRUCT_BEGIN\n");
188 205 NativeBootProgress.Stage("B80", "Preparing browser and document renderer");
189 206 var browser = new BrowserService(new NativeWebTransport()) { Trace = WriteDiagnostic };
@@ -245,10 +262,13 @@ public sealed class Kernel : Cosmos.Kernel.System.Kernel
245 262 {
246 263 // Driver-kit workers publish adapters asynchronously. The manager itself
247 264 // is initialized by Cosmos before the driver stage, not by this kernel.
248 for (var elapsed = 0; elapsed < 5_000 &&
249 (NetworkManager.DeviceCount == 0 || !NetworkManager.Ready || !NetworkManager.LinkUp); elapsed += 10)
250 TimerManager.Wait(10);
251 if (NetworkManager.DeviceCount == 0 || !NetworkManager.Ready || !NetworkManager.LinkUp)
265 long waitStarted = Stopwatch.GetTimestamp();
266 bool ready = NativeNetworkBootWait.WaitUntilReady(
267 () => NetworkManager.DeviceCount > 0 && NetworkManager.Ready && NetworkManager.LinkUp,
268 Stopwatch.GetTimestamp, Stopwatch.Frequency, Power.Halt);
269 WriteDiagnostic("XFE_NETWORK_BOOT_WAIT|Ready|" + ready + "|ElapsedTicks|" + (Stopwatch.GetTimestamp() - waitStarted) +
270 "|Frequency|" + Stopwatch.Frequency + "|PitWait|0\n");
271 if (!ready)
252 272 {
253 273 WriteDiagnostic("XFE_NETWORK_UNAVAILABLE\n");
254 274 return;
@@ -278,6 +298,10 @@ public sealed class Kernel : Cosmos.Kernel.System.Kernel
278 298 // would starve it, so the UI must itself be an ordinary runnable thread.
279 299 uiThread = new System.Threading.Thread(RunDesktopLoop, NativeManagedWorkScheduler.StackBytes);
280 300 uiThread.Start();
301 // Adapter and DHCP waits cannot hold the desktop's boot or UI thread.
302 // The thread/runtime contract has already passed before either starts.
303 networkThread = new System.Threading.Thread(InitializeNetwork, NativeManagedWorkScheduler.StackBytes);
304 networkThread.Start();
281 305 while (true) Power.Halt();
282 306 }
283 307
Modified Source/XFESystem.Kernel/NativeBootProgress.cs +17 -2
@@ -6,7 +6,7 @@ namespace XFESystem.Kernel;
6 6 internal static class NativeBootProgress
7 7 {
8 8 private static bool visible;
9 private static string stage = "B00";
9 private static string stage = "A00";
10 10 internal static int StageCount { get; private set; }
11 11 internal static bool IsVisible => visible;
12 12
@@ -28,13 +28,28 @@ internal static class NativeBootProgress
28 28 [SuppressGCTransition]
29 29 internal static extern ulong SerialDroppedCount();
30 30
31 [DllImport("*", EntryPoint = "XfeRuntimePhysicalCpuCount")]
32 [SuppressGCTransition]
33 internal static extern uint PhysicalCpuCount();
34 [DllImport("*", EntryPoint = "XfeRuntimeSchedulerCpuCount")]
35 [SuppressGCTransition]
36 internal static extern uint SchedulerCpuCount();
37
31 38 internal static void Begin()
32 39 {
33 40 SetEnabled(1);
34 41 visible = true;
35 42 StageCount = 0;
36 43 Write("\nXFESystem startup diagnostics\n");
37 Stage("B00", "Managed kernel entered");
44 Stage("A00", "Kernel lifecycle entered");
45 }
46
47 internal static void Resume(string code, string description)
48 {
49 // The base lifecycle disables its early mirror after OnBoot.
50 SetEnabled(1);
51 visible = true;
52 Stage(code, description);
38 53 }
39 54
40 55 internal static void Stage(string code, string description)
Added Source/XFESystem.Kernel/NativeNetworkBootWait.cs +39 -0
@@ -0,0 +1,39 @@
1 namespace XFESystem.Kernel;
2
3 /// <summary>
4 /// Bounds adapter discovery with the monotonic clock, independently of the PIT
5 /// and its software timer registry. The caller supplies the already-running
6 /// LAPIC interrupt wait; neither delegate may block indefinitely.
7 /// </summary>
8 internal static class NativeNetworkBootWait
9 {
10 internal const int TimeoutSeconds = 5;
11
12 internal static bool WaitUntilReady(Func<bool> pollReady, Func<long> monotonicTimestamp,
13 long timestampFrequency, Action waitOneInterrupt)
14 {
15 ArgumentNullException.ThrowIfNull(pollReady);
16 ArgumentNullException.ThrowIfNull(monotonicTimestamp);
17 ArgumentNullException.ThrowIfNull(waitOneInterrupt);
18 // Keep the budget below half the counter range. This permits an
19 // unsigned elapsed subtraction across signed Stopwatch wrap without
20 // overflowing an absolute deadline or accepting a backwards clock.
21 if (timestampFrequency <= 0 || timestampFrequency > long.MaxValue / TimeoutSeconds)
22 throw new ArgumentOutOfRangeException(nameof(timestampFrequency));
23
24 ulong budget = (ulong)timestampFrequency * TimeoutSeconds;
25 long started = monotonicTimestamp();
26 while (true)
27 {
28 if (Elapsed(monotonicTimestamp(), started) >= budget) return false;
29 bool ready = pollReady();
30 // Count time spent polling as part of the same total budget. A
31 // readiness result obtained at or after the deadline is too late.
32 if (Elapsed(monotonicTimestamp(), started) >= budget) return false;
33 if (ready) return true;
34 waitOneInterrupt();
35 }
36 }
37
38 private static ulong Elapsed(long current, long started) => unchecked((ulong)(current - started));
39 }
Modified Source/XFESystem.Kernel/XFESystem.Kernel.csproj +5 -0
@@ -23,6 +23,8 @@
23 23 <XfeEnableGcTailPatch Condition="'$(XfeEnableGcTailPatch)' == ''">true</XfeEnableGcTailPatch>
24 24 </PropertyGroup>
25 25 <ItemGroup>
26 <!-- The two topology diagnostics are emitted by the project-local HAL patch. -->
27 <UnmanagedEntryPointsAssembly Include="Cosmos.Kernel.HAL.X64" />
26 28 <AsmFiles Include="../../Tools/GcRuntimePatch/NativeThreadEh.s" />
27 29 <PackageReference Include="Cosmos.Kernel" Version="3.0.94" />
28 30 <PackageReference Include="Cosmos.Kernel.System" Version="3.0.94" />
@@ -56,4 +58,7 @@
56 58 <Target Name="XfeRestorePreciseGcTail" AfterTargets="XfeRestoreNativeJintCleanup" BeforeTargets="WriteIlcRsp" Condition="'$(XfeEnableGcTailPatch)' == 'true'">
57 59 <Exec Command="dotnet run --project &quot;$(MSBuildThisFileDirectory)../../Tools/GcRuntimePatch/GcRuntimePatch.csproj&quot; -c Release -- &quot;$(MSBuildThisFileDirectory)$(IntermediateOutputPath)/cosmos/ref/Cosmos.Kernel.Core.dll&quot;" />
58 60 </Target>
61 <Target Name="XfeBindSingleCpuBootPlatform" AfterTargets="XfeRestorePreciseGcTail" BeforeTargets="WriteIlcRsp">
62 <Exec Command="dotnet run --project &quot;$(MSBuildThisFileDirectory)../../Tools/BootPlatformPatch/BootPlatformPatch.csproj&quot; -c Release -- &quot;$(MSBuildThisFileDirectory)$(IntermediateOutputPath)/cosmos/ref&quot;" />
63 </Target>
59 64 </Project>
Added Tests/XFESystem.Core.Tests/NetworkBootWaitTests.cs +120 -0
@@ -0,0 +1,120 @@
1 using XFESystem.Kernel;
2 using Xunit;
3
4 namespace XFESystem.Core.Tests;
5
6 public sealed class NetworkBootWaitTests
7 {
8 [Fact]
9 public void AlreadyReadyDoesNotWaitForAnyInterrupt()
10 {
11 Assert.True(NativeNetworkBootWait.WaitUntilReady(() => true, () => 42, 1_000,
12 () => throw new InvalidOperationException("No wait expected.")));
13 }
14
15 [Fact]
16 public void NeverReadyTimesOutAfterFiveSecondsWithoutPitTicks()
17 {
18 long now = 0;
19 int lapicInterrupts = 0, polls = 0;
20 Assert.False(NativeNetworkBootWait.WaitUntilReady(() => { polls++; return false; }, () => now, 1_000,
21 () => { lapicInterrupts++; now += 10; }));
22 Assert.Equal(5_000, now);
23 Assert.Equal(500, lapicInterrupts);
24 Assert.Equal(500, polls);
25 }
26
27 [Theory]
28 [InlineData(4_990, true)]
29 [InlineData(5_000, false)]
30 [InlineData(5_010, false)]
31 public void ReadinessMustArriveBeforeTheDeadline(int readyAt, bool expected)
32 {
33 long now = 0;
34 Assert.Equal(expected, NativeNetworkBootWait.WaitUntilReady(() => now >= readyAt, () => now, 1_000,
35 () => now += 10));
36 Assert.Equal(expected ? readyAt : 5_000, now);
37 }
38
39 [Fact]
40 public void LongInterruptWaitCountsTowardTheSameBudget()
41 {
42 long now = 0;
43 int polls = 0, waits = 0;
44 Assert.False(NativeNetworkBootWait.WaitUntilReady(() => { polls++; return false; }, () => now, 1_000,
45 () => { waits++; now += 9_000; }));
46 Assert.Equal(1, waits);
47 Assert.Equal(1, polls);
48 }
49
50 [Theory]
51 [InlineData(true)]
52 [InlineData(false)]
53 public void PollingTimeCannotExtendTheDeadline(bool ready)
54 {
55 long now = 0;
56 Assert.False(NativeNetworkBootWait.WaitUntilReady(() => { now += 5_000; return ready; }, () => now, 1_000,
57 () => throw new InvalidOperationException("An expired poll must not wait.")));
58 }
59
60 [Theory]
61 [InlineData(1)]
62 [InlineData(1_000)]
63 [InlineData(1_000_000_001)]
64 public void UsesTheSuppliedTimestampFrequency(long frequency)
65 {
66 long now = 0;
67 int waits = 0;
68 Assert.False(NativeNetworkBootWait.WaitUntilReady(() => false, () => now, frequency,
69 () => { waits++; now += frequency; }));
70 Assert.Equal(5, waits);
71 Assert.Equal(frequency * 5, now);
72 }
73
74 [Theory]
75 [InlineData(long.MaxValue - 2_000)]
76 [InlineData(-2_000)]
77 public void TimestampWrapDoesNotOverflowOrSkipTheTimeout(long start)
78 {
79 long now = start;
80 int waits = 0;
81 Assert.False(NativeNetworkBootWait.WaitUntilReady(() => false, () => now, 1_000,
82 () => { waits++; now = unchecked(now + 1_000); }));
83 Assert.Equal(5, waits);
84 Assert.Equal(unchecked(start + 5_000), now);
85 }
86
87 [Fact]
88 public void LargestSupportedBudgetDoesNotOverflow()
89 {
90 const long frequency = long.MaxValue / 5;
91 long now = 100;
92 int waits = 0;
93 Assert.False(NativeNetworkBootWait.WaitUntilReady(() => false, () => now, frequency,
94 () => { waits++; now = unchecked(now + frequency * 5); }));
95 Assert.Equal(1, waits);
96 }
97
98 [Fact]
99 public void BackwardsClockFailsClosed()
100 {
101 long now = 100;
102 int waits = 0;
103 Assert.False(NativeNetworkBootWait.WaitUntilReady(() => false, () => now, 1_000,
104 () => { waits++; now--; }));
105 Assert.Equal(1, waits);
106 }
107
108 [Theory]
109 [InlineData(0)]
110 [InlineData(-1)]
111 [InlineData(long.MaxValue / 5 + 1)]
112 [InlineData(long.MaxValue)]
113 public void InvalidOrUnrepresentableFrequencyIsRejectedBeforeWaiting(long frequency)
114 {
115 Assert.Throws<ArgumentOutOfRangeException>(() => NativeNetworkBootWait.WaitUntilReady(
116 () => throw new InvalidOperationException("No poll expected."),
117 () => throw new InvalidOperationException("No timestamp expected."), frequency,
118 () => throw new InvalidOperationException("No wait expected.")));
119 }
120 }
Modified Tests/XFESystem.Core.Tests/XFESystem.Core.Tests.csproj +1 -0
@@ -9,6 +9,7 @@
9 9 <NuGetLockFilePath>Packages.lock.json</NuGetLockFilePath>
10 10 </PropertyGroup>
11 11 <ItemGroup>
12 <Compile Include="../../Source/XFESystem.Kernel/NativeNetworkBootWait.cs" Link="NativeNetworkBootWait.cs" />
12 13 <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.8.0" />
13 14 <PackageReference Include="xunit" Version="2.9.3" />
14 15 <PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" />
Added Tools/BootPlatformPatch/BootPlatformPatch.csproj +14 -0
@@ -0,0 +1,14 @@
1 <Project Sdk="Microsoft.NET.Sdk">
2 <PropertyGroup>
3 <TargetFramework>net10.0</TargetFramework>
4 <OutputType>Exe</OutputType>
5 <ImplicitUsings>enable</ImplicitUsings>
6 <Nullable>enable</Nullable>
7 <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
8 <NuGetLockFilePath>Packages.lock.json</NuGetLockFilePath>
9 </PropertyGroup>
10 <ItemGroup>
11 <PackageReference Include="Mono.Cecil" Version="0.11.6" />
12 <Compile Remove="Tests/**/*.cs" />
13 </ItemGroup>
14 </Project>
Added Tools/BootPlatformPatch/Packages.lock.json +13 -0
@@ -0,0 +1,13 @@
1 {
2 "version": 1,
3 "dependencies": {
4 "net10.0": {
5 "Mono.Cecil": {
6 "type": "Direct",
7 "requested": "[0.11.6, )",
8 "resolved": "0.11.6",
9 "contentHash": "f33RkDtZO8VlGXCtmQIviOtxgnUdym9xx/b1p9h91CRGOsJFxCFOFK1FDbVt1OCf1aWwYejUFa2MOQyFWTFjbA=="
10 }
11 }
12 }
13 }
Added Tools/BootPlatformPatch/PlatformPatch.cs +157 -0
@@ -0,0 +1,157 @@
1 using System.Globalization;
2 using System.Runtime.InteropServices;
3 using System.Security.Cryptography;
4 using System.Text;
5 using Mono.Cecil;
6 using Mono.Cecil.Cil;
7
8 namespace XfeBootPlatformPatch;
9
10 /// <summary>The executing machine has one boot processor; application processors are not started.</summary>
11 public static class PlatformPatch
12 {
13 public const string PackageVersion = "3.0.94";
14 public const string CoreName = "Cosmos.Kernel.Core.X64";
15 public const string HalName = "Cosmos.Kernel.HAL.X64";
16 public const string CoreOriginalSha256 = "95359dc648a01a2e31ae5fbb513efa3fd796acebd55b69c244c9d4ab988578c0";
17 public const string HalOriginalSha256 = "6267612213cbf40a3b20bf56a763652f9075363fb2561d71036d4fe23b164efd";
18 public const string PhysicalExport = "XfeRuntimePhysicalCpuCount";
19 public const string SchedulerExport = "XfeRuntimeSchedulerCpuCount";
20 private const string OriginalTimer = "97693c4fa91997732b61aadc543425be326814a3a7fe5ecbd6fe14316dd70871";
21 private const string PatchedTimer = "948a18f6c44247740881c219567b4b1830bfc5dbeb8b133350412bbd70335500";
22 private const string OriginalCount = "76314847f293220be85f14b146ed36f9447ebf583898dff04fb0bc21b91600dc";
23 private const string PatchedCount = "b9bd48603446583f1db361633209435df227265e38f3883d1a04f2c73bfe01ba";
24 private const string OriginalDispatch = "8c41e004b866acbece6ab3a79f1fe856562b7900732a2ba254591233c90f08e5";
25 private const string PatchedDispatch = "5e50d316eafcc7d1a4f11aee6dbb0c560a230d25cf1bbbf1decdd517cc844d37";
26 private const string PhysicalMethod = "XfePhysicalCpuCountV1";
27 private const string SchedulerMethod = "XfeSchedulerCpuCountV1";
28
29 public static void Apply(AssemblyDefinition core, AssemblyDefinition hal)
30 {
31 Versions(core, hal);
32 var timer = Timer(core);
33 var count = Count(hal);
34 var dispatch = Dispatch(core);
35 if (Fingerprint(timer) != OriginalTimer || Fingerprint(count) != OriginalCount || Fingerprint(dispatch) != OriginalDispatch)
36 throw new InvalidOperationException("Pinned platform IL changed; refusing to guess a patch location.");
37 if (hal.MainModule.Types.SelectMany(type => type.Methods).Any(method =>
38 method.Name is PhysicalMethod or SchedulerMethod || method.CustomAttributes.Any(attribute =>
39 attribute.AttributeType.FullName == typeof(UnmanagedCallersOnlyAttribute).FullName &&
40 attribute.Fields.Any(field => field.Name == "EntryPoint" && field.Argument.Value is string name &&
41 name is PhysicalExport or SchedulerExport))))
42 throw new InvalidOperationException("A platform diagnostic export name is already used.");
43
44 // Retain the exact ACPI getter reference for a read-only physical-topology diagnostic.
45 var physical = (MethodReference)count.Body.Instructions[0].Operand;
46 count.Body.Instructions.Clear();
47 var countIl = count.Body.GetILProcessor();
48 countIl.Append(countIl.Create(OpCodes.Ldc_I4_1));
49 countIl.Append(countIl.Create(OpCodes.Ret));
50 AddExport(hal, PhysicalMethod, PhysicalExport, physical);
51 AddExport(hal, SchedulerMethod, SchedulerExport, null);
52
53 var cpu = timer.Body.Instructions.Single(instruction => instruction.Offset == 0x0c);
54 cpu.OpCode = OpCodes.Ldc_I4_0;
55 cpu.Operand = null;
56 var schedulerArguments = timer.Body.Instructions.Single(instruction => instruction.Offset == 0x105);
57 var validStack = timer.Body.Instructions.Single(instruction => instruction.Offset == 0x35);
58 validStack.Operand = schedulerArguments;
59 // Remove the complete normal first-five/every-100-ticks output block, including
60 // params object boxing, hexadecimal RSP, and newline. Error diagnostics stay intact.
61 var removed = timer.Body.Instructions.Where(instruction => instruction.Offset >= 0x95 && instruction.Offset < 0x105).ToArray();
62 foreach (var instruction in removed) timer.Body.Instructions.Remove(instruction);
63 // Device-IRQ exit uses the same logical index as the timer. A hardware
64 // APIC ID is still used by unchanged routing code, never as this array index.
65 var deviceCpu = dispatch.Body.Instructions.Single(instruction => instruction.Offset == 0x73);
66 deviceCpu.OpCode = OpCodes.Ldc_I4_0;
67 deviceCpu.Operand = null;
68 Verify(core, hal);
69 }
70
71 public static void Verify(AssemblyDefinition core, AssemblyDefinition hal)
72 {
73 Versions(core, hal);
74 var timer = Timer(core);
75 var count = Count(hal);
76 var dispatch = Dispatch(core);
77 if (Fingerprint(timer) != PatchedTimer || Fingerprint(count) != PatchedCount || Fingerprint(dispatch) != PatchedDispatch)
78 throw new InvalidOperationException("The installed platform patch differs from its fixed IL contract.");
79 var instructions = timer.Body.Instructions;
80 if (instructions.Count(instruction => instruction.Operand is MethodReference method && method.Name == "OnTimerInterrupt" &&
81 method.DeclaringType.FullName == "Cosmos.Kernel.Core.Scheduler.SchedulerManager") != 1 ||
82 instructions.Any(instruction => instruction.Operand is string text && text == "[LAPIC] Timer tick ") ||
83 instructions.Any(instruction => instruction.Operand is MethodReference method && method.Name is "GetId" or "SendEOI") ||
84 instructions.Count(instruction => instruction.Operand is string text && text == "[LAPIC] ERROR: Invalid RSP at tick ") != 1)
85 throw new InvalidOperationException("The timer's tick, error, logical-CPU or EOI contract changed.");
86 if (dispatch.Body.Instructions.Count(instruction => instruction.Operand is MethodReference method && method.Name == "ReschedulePendingFromIrq" &&
87 method.DeclaringType.FullName == "Cosmos.Kernel.Core.Scheduler.SchedulerManager") != 1 ||
88 dispatch.Body.Instructions.Count(instruction => instruction.Operand is MethodReference method && method.Name == "SendEOI") != 2 ||
89 dispatch.Body.Instructions.Any(instruction => instruction.Operand is MethodReference method && method.Name == "GetId"))
90 throw new InvalidOperationException("The device-IRQ scheduler index or original EOI paths changed.");
91 VerifyExport(hal, PhysicalMethod, PhysicalExport, true);
92 VerifyExport(hal, SchedulerMethod, SchedulerExport, false);
93 }
94
95 public static string Fingerprint(MethodDefinition method)
96 {
97 string Operand(object? operand) => operand switch
98 {
99 Instruction branch => "#" + method.Body.Instructions.IndexOf(branch),
100 VariableDefinition local => "$" + local.Index,
101 MemberReference member => member.FullName,
102 null => "",
103 IFormattable value => value.ToString(null, CultureInfo.InvariantCulture),
104 _ => operand.ToString()!
105 };
106 var value = string.Join("|", method.IsStatic, method.ReturnType.FullName,
107 string.Join(";", method.Parameters.Select(parameter => parameter.ParameterType.FullName)),
108 method.Body.InitLocals, method.Body.ExceptionHandlers.Count,
109 string.Join(";", method.Body.Variables.Select(local => local.VariableType.FullName)),
110 string.Join("\n", method.Body.Instructions.Select(instruction => instruction.OpCode.Code + ":" + Operand(instruction.Operand))));
111 return Hash(Encoding.UTF8.GetBytes(value));
112 }
113
114 public static string Hash(byte[] bytes) => Convert.ToHexStringLower(SHA256.HashData(bytes));
115 private static TypeDefinition Initializer(AssemblyDefinition hal) => hal.MainModule.Types.Single(type => type.FullName == "Cosmos.Kernel.HAL.X64.X64PlatformInitializer");
116 public static MethodDefinition Count(AssemblyDefinition hal) => Initializer(hal).Methods.Single(method => method.Name == "GetCpuCount");
117 public static MethodDefinition Timer(AssemblyDefinition core) => core.MainModule.Types.Single(type => type.FullName == "Cosmos.Kernel.Core.X64.Cpu.LocalApic").Methods.Single(method => method.Name == "HandleTimerInterrupt");
118 public static MethodDefinition Dispatch(AssemblyDefinition core) => core.MainModule.Types.Single(type => type.FullName == "Cosmos.Kernel.Core.X64.Cpu.X64InterruptController").Methods.Single(method => method.Name == "Dispatch");
119 private static void Versions(AssemblyDefinition core, AssemblyDefinition hal)
120 {
121 if (core.Name.Name != CoreName || hal.Name.Name != HalName || core.Name.Version != new Version(3, 0, 94, 0) || hal.Name.Version != new Version(3, 0, 94, 0))
122 throw new InvalidOperationException("Only the pinned Cosmos x64 platform 3.0.94 is supported.");
123 if (!Timer(core).HasBody || !Count(hal).HasBody || !Dispatch(core).HasBody || Timer(core).Parameters.Count != 1 ||
124 Count(hal).Parameters.Count != 0 || Dispatch(core).Parameters.Count != 1)
125 throw new InvalidOperationException("Pinned platform method signatures changed.");
126 }
127
128 private static void AddExport(AssemblyDefinition hal, string name, string entry, MethodReference? physical)
129 {
130 var module = hal.MainModule;
131 var method = new MethodDefinition(name, MethodAttributes.Assembly | MethodAttributes.Static | MethodAttributes.HideBySig, module.TypeSystem.UInt32);
132 var attribute = new CustomAttribute(module.ImportReference(typeof(UnmanagedCallersOnlyAttribute).GetConstructor(Type.EmptyTypes)!));
133 attribute.Fields.Add(new("EntryPoint", new CustomAttributeArgument(module.TypeSystem.String, entry)));
134 method.CustomAttributes.Add(attribute);
135 Initializer(hal).Methods.Add(method);
136 var il = method.Body.GetILProcessor();
137 il.Append(physical is null ? il.Create(OpCodes.Ldc_I4_1) : il.Create(OpCodes.Call, physical));
138 il.Append(il.Create(OpCodes.Ret));
139 method.Body.MaxStackSize = 1;
140 }
141
142 private static void VerifyExport(AssemblyDefinition hal, string name, string entry, bool physical)
143 {
144 var method = Initializer(hal).Methods.Single(candidate => candidate.Name == name);
145 if (!method.IsAssembly || !method.IsStatic || method.ReturnType.MetadataType != MetadataType.UInt32 || method.Parameters.Count != 0 ||
146 !method.HasBody || method.Body.InitLocals || method.Body.Variables.Count != 0 || method.Body.ExceptionHandlers.Count != 0 ||
147 method.Body.Instructions.Count != 2 || method.Body.Instructions[1].OpCode != OpCodes.Ret ||
148 method.CustomAttributes.Count != 1 || method.CustomAttributes[0].AttributeType.FullName != typeof(UnmanagedCallersOnlyAttribute).FullName ||
149 method.CustomAttributes[0].Fields.Count != 1 || method.CustomAttributes[0].Fields[0].Name != "EntryPoint" ||
150 !Equals(method.CustomAttributes[0].Fields[0].Argument.Value, entry))
151 throw new InvalidOperationException("The platform diagnostic export signature changed.");
152 var first = method.Body.Instructions[0];
153 if (physical ? first.OpCode != OpCodes.Call || first.Operand is not MethodReference getter ||
154 getter.FullName != "System.UInt32 Cosmos.Kernel.Core.X64.AcpiMadt::get_CpuCount()" : first.OpCode != OpCodes.Ldc_I4_1)
155 throw new InvalidOperationException("The platform diagnostic is not its expected read-only leaf.");
156 }
157 }
Added Tools/BootPlatformPatch/PrivatePlatformFiles.cs +155 -0
@@ -0,0 +1,155 @@
1 using System.Text;
2 using System.Text.Json;
3 using Mono.Cecil;
4
5 namespace XfeBootPlatformPatch;
6
7 /// <summary>Fail-closed patching of private compiler references, never the NuGet package cache.</summary>
8 public static class PrivatePlatformFiles
9 {
10 public const string ReceiptName = "XfeBootPlatformV1.json";
11 public const string PendingName = "XfeBootPlatformV1.Pending.json";
12 private const string LockName = "XfeBootPlatformV1.lock";
13 private const string Kind = "ProjectLocalBootProcessorPlatformV1";
14 private static readonly JsonSerializerOptions Json = new() { WriteIndented = true };
15
16 public static void Run(string directory)
17 {
18 directory = ValidateDirectory(directory);
19 var corePath = Path.Combine(directory, PlatformPatch.CoreName + ".dll");
20 var halPath = Path.Combine(directory, PlatformPatch.HalName + ".dll");
21 var receiptPath = Path.Combine(directory, ReceiptName);
22 var pendingPath = Path.Combine(directory, PendingName);
23 var lockPath = Path.Combine(directory, LockName);
24 RejectReparse(corePath); RejectReparse(halPath);
25 RejectReparseIfPresent(receiptPath); RejectReparseIfPresent(pendingPath); RejectReparseIfPresent(lockPath);
26 using var locked = new FileStream(lockPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
27 if (File.Exists(pendingPath))
28 throw new InvalidOperationException("An interrupted platform write exists. Restore BOTH official private reference DLLs and inspect the pending receipt before retrying.");
29 var coreBytes = File.ReadAllBytes(corePath);
30 var halBytes = File.ReadAllBytes(halPath);
31 var coreHash = PlatformPatch.Hash(coreBytes);
32 var halHash = PlatformPatch.Hash(halBytes);
33 var toolHash = PlatformPatch.Hash(File.ReadAllBytes(typeof(PrivatePlatformFiles).Assembly.Location));
34 using var core = Read(coreBytes);
35 using var hal = Read(halBytes);
36 bool originalCore = coreHash == PlatformPatch.CoreOriginalSha256;
37 bool originalHal = halHash == PlatformPatch.HalOriginalSha256;
38 if (!originalCore || !originalHal)
39 {
40 if (originalCore != originalHal || !File.Exists(receiptPath))
41 throw new InvalidOperationException("Unknown or partly patched platform references are not trusted.");
42 var receipt = JsonSerializer.Deserialize<Receipt>(File.ReadAllText(receiptPath, Encoding.UTF8)) ?? throw new InvalidOperationException("Empty platform receipt.");
43 if (receipt.Kind != Kind || receipt.PackageVersion != PlatformPatch.PackageVersion ||
44 receipt.CoreOriginalSha256 != PlatformPatch.CoreOriginalSha256 || receipt.HalOriginalSha256 != PlatformPatch.HalOriginalSha256 ||
45 receipt.CoreAfterSha256 != coreHash || receipt.HalAfterSha256 != halHash || receipt.ToolSha256 != toolHash ||
46 !string.Equals(receipt.PrivateDirectory, directory, StringComparison.OrdinalIgnoreCase) || receipt.SchedulerCpuCount != 1 ||
47 !receipt.NormalTimerLogRemoved || !receipt.HardwareApicRoutingPreserved ||
48 !receipt.DeviceIrqSchedulerUsesLogicalCpuZero || receipt.SchedulerCpuIndex != 0 || receipt.SchedulerCpuIndexSitesPatched != 2)
49 throw new InvalidOperationException("Private platform references do not match their pinned-origin verification receipt.");
50 PlatformPatch.Verify(core, hal);
51 Console.WriteLine("XFE boot processor platform patch verified; no files changed.");
52 return;
53 }
54
55 // All input and IL guards finish before any destination or receipt is modified.
56 PlatformPatch.Apply(core, hal);
57 var afterCore = Write(core);
58 var afterHal = Write(hal);
59 using (var roundCore = Read(afterCore))
60 using (var roundHal = Read(afterHal)) PlatformPatch.Verify(roundCore, roundHal);
61 var next = new Receipt
62 {
63 Kind = Kind, PackageVersion = PlatformPatch.PackageVersion, PrivateDirectory = directory,
64 CoreOriginalSha256 = PlatformPatch.CoreOriginalSha256, HalOriginalSha256 = PlatformPatch.HalOriginalSha256,
65 CoreAfterSha256 = PlatformPatch.Hash(afterCore), HalAfterSha256 = PlatformPatch.Hash(afterHal),
66 ToolSha256 = toolHash, SchedulerCpuCount = 1, NormalTimerLogRemoved = true,
67 HardwareApicRoutingPreserved = true, DeviceIrqSchedulerUsesLogicalCpuZero = true,
68 SchedulerCpuIndex = 0, SchedulerCpuIndexSitesPatched = 2
69 };
70 byte[] receiptBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(next, Json));
71 if (PlatformPatch.Hash(File.ReadAllBytes(corePath)) != coreHash || PlatformPatch.Hash(File.ReadAllBytes(halPath)) != halHash)
72 throw new IOException("Platform input changed during verification.");
73 // Two renames cannot form one filesystem transaction. The durable pending marker is
74 // written first and remains on ANY failure; later builds then reject the partial pair.
75 using (var pending = new FileStream(pendingPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, 4096, FileOptions.WriteThrough))
76 { pending.Write(receiptBytes); pending.Flush(true); }
77 Replace(corePath, afterCore);
78 Replace(halPath, afterHal);
79 if (PlatformPatch.Hash(File.ReadAllBytes(corePath)) != next.CoreAfterSha256 || PlatformPatch.Hash(File.ReadAllBytes(halPath)) != next.HalAfterSha256)
80 throw new IOException("Platform output readback differs from the verified pair.");
81 Replace(receiptPath, receiptBytes);
82 File.Delete(pendingPath);
83 Console.WriteLine("XFE boot processor platform ready: one executing BSP, logical CPU 0; physical ACPI topology and APIC routing unchanged.");
84 Console.WriteLine("XFE normal timer ISR logging removed; tick counter, invalid-stack diagnostics and scheduler dispatch preserved. Native acceptance still required.");
85 }
86
87 public static string ValidateDirectory(string path)
88 {
89 var directory = Path.TrimEndingDirectorySeparator(Path.GetFullPath(path));
90 if (!Directory.Exists(directory) || !directory.EndsWith(Path.Combine("cosmos", "ref"), StringComparison.OrdinalIgnoreCase))
91 throw new ArgumentException("Only a private Kernel obj/.../cosmos/ref directory is accepted.");
92 string? projectRoot = directory;
93 while (projectRoot is not null && !File.Exists(Path.Combine(projectRoot, "XFESystem.Kernel.csproj")))
94 projectRoot = Path.GetDirectoryName(projectRoot);
95 if (projectRoot is null || !directory.StartsWith(Path.Combine(projectRoot, "obj") + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
96 throw new ArgumentException("Platform patches must remain under the kernel project's private obj directory.");
97 // Check the entire resolved textual ancestor chain, including the project and drive,
98 // rather than stopping just below the project and accepting a junction above it.
99 for (string? current = directory; current is not null; current = Path.GetDirectoryName(current)) RejectReparse(current);
100 return directory;
101 }
102
103 private static void RejectReparseIfPresent(string path)
104 {
105 // File.Exists is false for a dangling symlink. GetAttributes still sees
106 // the link itself, so it cannot redirect an OpenOrCreate lock outside obj.
107 try { RejectReparse(path); }
108 catch (FileNotFoundException) { }
109 catch (DirectoryNotFoundException) { }
110 }
111
112 private static void RejectReparse(string path)
113 {
114 if ((File.GetAttributes(path) & FileAttributes.ReparsePoint) != 0)
115 throw new ArgumentException("A private platform path cannot traverse a symbolic link or junction.");
116 }
117
118 private static AssemblyDefinition Read(byte[] bytes) => AssemblyDefinition.ReadAssembly(new MemoryStream(bytes, false), new ReaderParameters { InMemory = true });
119 private static byte[] Write(AssemblyDefinition assembly)
120 {
121 using var stream = new MemoryStream();
122 assembly.Write(stream);
123 return stream.ToArray();
124 }
125 private static void Replace(string path, byte[] bytes)
126 {
127 // CreateNew and a fresh name ensure cleanup can only remove our own temporary file.
128 var temporary = path + "." + Guid.NewGuid().ToString("N") + ".tmp";
129 try
130 {
131 using (var output = new FileStream(temporary, FileMode.CreateNew, FileAccess.Write, FileShare.None, 4096, FileOptions.WriteThrough))
132 { output.Write(bytes); output.Flush(true); }
133 File.Move(temporary, path, true);
134 }
135 finally { if (File.Exists(temporary)) File.Delete(temporary); }
136 }
137 public sealed class Receipt
138 {
139 public string Kind { get; set; } = "";
140 public string PackageVersion { get; set; } = "";
141 public string PrivateDirectory { get; set; } = "";
142 public string CoreOriginalSha256 { get; set; } = "";
143 public string HalOriginalSha256 { get; set; } = "";
144 public string CoreAfterSha256 { get; set; } = "";
145 public string HalAfterSha256 { get; set; } = "";
146 public string ToolSha256 { get; set; } = "";
147 public uint SchedulerCpuCount { get; set; }
148 public bool NormalTimerLogRemoved { get; set; }
149 public bool HardwareApicRoutingPreserved { get; set; }
150 public bool DeviceIrqSchedulerUsesLogicalCpuZero { get; set; }
151 public uint SchedulerCpuIndex { get; set; }
152 public int SchedulerCpuIndexSitesPatched { get; set; }
153 public bool NativeGuestTested { get; set; }
154 }
155 }
Added Tools/BootPlatformPatch/Program.cs +3 -0
@@ -0,0 +1,3 @@
1 if (args.Length != 1)
2 throw new ArgumentException("BootPlatformPatch <private Kernel obj/.../cosmos/ref directory>");
3 XfeBootPlatformPatch.PrivatePlatformFiles.Run(args[0]);
Added Tools/BootPlatformPatch/README.md +36 -0
@@ -0,0 +1,36 @@
1 # x64 启动处理器与时钟中断修补
2
3 本工具仅改写 `Source/XFESystem.Kernel/obj/.../cosmos/ref` 下的两份私有引用,不修改 NuGet 包、ACPI 物理 CPU 枚举、硬件 APIC 路由、其他设备的中断或 EFI。
4
5 固定 Cosmos 3.0.94 只运行 BSP,尚未启动 AP。HAL 原先向调度器报告 ACPI 中所有 CPU,调度器却固定采用逻辑 CPU 0;项目的线程 EH 合约又仅支持一个实际运行的 CPU。本工具将 `X64PlatformInitializer.GetCpuCount()` 改为返回 1,并将 LAPIC timer ISR 和设备 IRQ 尾部 `X64InterruptController.Dispatch` 传给调度器的两个索引统一改为逻辑 0。后者仅替换 `ReschedulePendingFromIrq` 的 CPU 参数,其正常/未处理 IRQ 的两处 EOI、spurious vector 保护及异常分支均完整保留,由整个 Dispatch 方法的原始/结果 IL 指纹约束。原 `LocalApic.GetId()` 方法保持字节码不变,其他路由调用仍取得真实硬件 APIC ID。
6
7 ISR 原先 first-five/every-100-ticks 正常日志块会在关闭中断时装箱对象、轮询串口并写固件 framebuffer。本工具物理移除该完整正常日志块,保留 tick 递增、保存的上下文地址计算、invalid RSP 检查和错误日志,以及恰好一次原 `SchedulerManager.OnTimerInterrupt` 调用。ISR 原先不负责 EOI,修补后仍由原 `InterruptManager.Dispatch` 负责,不新增或吞掉 EOI。错误分支保留原诊断及其装箱行为;本补丁不声称所有 ISR 均无分配。
8
9 新增两个无参数、只读叶导出,返回类型都是 **uint32**:
10
11 - `XfeRuntimePhysicalCpuCount`:读取未改动的 `AcpiMadt.CpuCount`,表示固件描述的物理执行单元数。
12 - `XfeRuntimeSchedulerCpuCount`:返回 1,表示本版本实际建立的 BSP 调度模型。
13
14 ## 运行
15
16 在 Cosmos `RunPatcher` 完成后、NativeAOT `WriteIlcRsp` 之前调用;与现有 Core.dll GC/EH 补丁互不改写同一文件。
17
18 内核项目还必须包含 `<UnmanagedEntryPointsAssembly Include="Cosmos.Kernel.HAL.X64" />`。固定 SDK 原先仅为 Core 生成 unmanaged entry points;HAL 的 `--root` 只保留其初始化,不能代替这一项。遗漏时两个只读诊断导出会在原生链接阶段报 undefined symbol,不能当成验收通过。
19
20 ```powershell
21 dotnet run --project Tools/BootPlatformPatch/BootPlatformPatch.csproj -c Release -- C:/Absolute/Kernel/obj/Release/net10.0/linux-x64/cosmos/ref
22 dotnet run --project Tools/BootPlatformPatch/Tests/BootPlatformPatch.Check.csproj -c Release -- C:/Absolute/XFESystem
23 ```
24
25 Mono.Cecil 固定为 0.11.6,带依赖锁。只接受程序集身份 3.0.94.0、整个官方原始文件 SHA256 与目标方法完整 IL 指纹;所有未知形态、版本变化、损坏 receipt 或一半原始一半已修补的文件对均拒绝。完整祖先路径及输入、receipt、锁文件拒绝 symbolic link/junction,包括 dangling link。先完整验证并在内存生成、重新读回 Cecil 验证后才写入。
26
27 官方原始 SHA256:
28
29 - Core.X64:`95359dc648a01a2e31ae5fbb513efa3fd796acebd55b69c244c9d4ab988578c0`
30 - HAL.X64:`6267612213cbf40a3b20bf56a763652f9075363fb2561d71036d4fe23b164efd`
31
32 旁侧 `XfeBootPlatformV1.json` receipt 记录原始/结果 SHA256、工具 DLL SHA256、包版本、逻辑 CPU 数及修补范围;`SchedulerCpuIndex=0`、`SchedulerCpuIndexSitesPatched=2`、`DeviceIrqSchedulerUsesLogicalCpuZero=true` 绑定两个调度入口的相同契约。重复调用同时核对 receipt 与两份实际 DLL,不重新写文件或改变 receipt。Cosmos 重新复制两份精确官方 DLL 后可重新生成补丁;只恢复其中一份会拒绝。
33
34 两个文件重命名不是一个完整文件系统事务。工具先持久化 `XfeBootPlatformV1.Pending.json`,全部实际文件读回、receipt 原子替换成功后才移除该 marker。任何 I/O 异常会留下 marker,后续构建拒绝消费这一对文件;不能将部分写入当成成功。恢复时应先检查 marker/receipt,再由原 Cosmos `RunPatcher` 恢复**两份**官方私有 DLL;确认两份 SHA256 匹配上述原始值后再移除 pending marker,并重新运行。工具不提供跳过 guard 的强制选项。
35
36 宿主 fixture 检查真实官方程序集、Cecil 写出/重读、其他方法不变、正常/错误分支、CPU 索引、错误版本/变异 IL、实际文件 receipt、重复执行字节幂等及半提交拒绝。它不代替原生多核 ACPI 环境或实体机验收;没有创建测试镜像。
Added Tools/BootPlatformPatch/Tests/BootPlatformPatch.Check.csproj +13 -0
Added Tools/BootPlatformPatch/Tests/Packages.lock.json +18 -0
Added Tools/BootPlatformPatch/Tests/Program.cs +132 -0
Modified Tools/CSharpBoot/TestBoot.py +38 -2
Added Tools/GcRuntimePatch/PeriodicSchedulerLogPatch.cs +68 -0
Modified Tools/GcRuntimePatch/Program.cs +7 -1
Modified Tools/GcRuntimePatch/Tests/GcRuntimePatch.Check.csproj +1 -0
Added Tools/GcRuntimePatch/Tests/PeriodicSchedulerLogPatchChecks.cs +245 -0
Modified Tools/GcRuntimePatch/Tests/Program.cs +1 -0
Modified Tools/TestCSharp.ps1 +1 -1
Modified XFESystem.slnx +1 -0
Modified docs/CSharpPhysicalBootDiagnostics.md +17 -3