返回提交历史
Modified
Source/XFESystem.Kernel/Kernel.cs
+33
-9
Modified
Source/XFESystem.Kernel/NativeBootProgress.cs
+17
-2
Added
Source/XFESystem.Kernel/NativeNetworkBootWait.cs
+39
-0
Modified
Source/XFESystem.Kernel/XFESystem.Kernel.csproj
+5
-0
Added
Tests/XFESystem.Core.Tests/NetworkBootWaitTests.cs
+120
-0
Modified
Tests/XFESystem.Core.Tests/XFESystem.Core.Tests.csproj
+1
-0
Added
Tools/BootPlatformPatch/BootPlatformPatch.csproj
+14
-0
Added
Tools/BootPlatformPatch/Packages.lock.json
+13
-0
Added
Tools/BootPlatformPatch/PlatformPatch.cs
+157
-0
Added
Tools/BootPlatformPatch/PrivatePlatformFiles.cs
+155
-0
Added
Tools/BootPlatformPatch/Program.cs
+3
-0
Added
Tools/BootPlatformPatch/README.md
+36
-0
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
XFEstudio/XFESystem
Fix single-CPU execution on multicore hardware and isolate network startup
b2a7235
代码差异
24 个文件
+1135
-18
@@ -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
@@ -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)
@@ -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
}
@@ -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 "$(MSBuildThisFileDirectory)../../Tools/GcRuntimePatch/GcRuntimePatch.csproj" -c Release -- "$(MSBuildThisFileDirectory)$(IntermediateOutputPath)/cosmos/ref/Cosmos.Kernel.Core.dll"" />
58
60
</Target>
61
<Target Name="XfeBindSingleCpuBootPlatform" AfterTargets="XfeRestorePreciseGcTail" BeforeTargets="WriteIlcRsp">
62
<Exec Command="dotnet run --project "$(MSBuildThisFileDirectory)../../Tools/BootPlatformPatch/BootPlatformPatch.csproj" -c Release -- "$(MSBuildThisFileDirectory)$(IntermediateOutputPath)/cosmos/ref"" />
63
</Target>
59
64
</Project>
@@ -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
}
@@ -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" />
@@ -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>
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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]);
@@ -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 环境或实体机验收;没有创建测试镜像。