using System.Reflection; using System.Runtime.Loader; using Mono.Cecil; using Mono.Cecil.Cil; internal static class SerialRuntimePatchChecks { public static void Run(AssemblyDefinition before, AssemblyDefinition after, Action check) { var serial = after.MainModule.Types.Single(type => type.FullName == "Cosmos.Kernel.Core.IO.Serial"); var original = before.MainModule.Types.Single(type => type.FullName == serial.FullName); var helper = serial.Methods.Single(method => method.Name == SerialRuntimePatch.HelperName); check("SerialInitializationAndOtherWritersUnchanged", original.Methods.Where(method => method.Name != "ComWrite").All(method => Il(method) == Il(serial.Methods.Single(candidate => candidate.FullName == method.FullName)))); check("SerialFixedBudgetWithoutClockAllocationOrIrqChanges", helper.Body.Instructions.Any(instruction => instruction.OpCode == OpCodes.Ldc_I4 && Equals(instruction.Operand, 4096)) && helper.Body.ExceptionHandlers.Count == 0 && helper.Body.Instructions.All(instruction => instruction.OpCode.Code is not (Code.Newobj or Code.Newarr or Code.Throw or Code.Callvirt)) && helper.Body.Instructions.Where(instruction => instruction.Operand is MethodReference).All(instruction => instruction.Operand is MethodReference call && call.DeclaringType.FullName == "Cosmos.Kernel.Core.Native/IO" && call.Name is "Read8" or "Write8")); check("SerialReadOnlyCounterExports", new[] { "Absent", "Timeout", "Dropped", "Written" }.All(name => { var getter = serial.Methods.Single(method => method.Name == "XfeSerial" + name + "CountV1"); return getter.Body.Instructions.Count == 2 && getter.Body.Instructions[0].OpCode == OpCodes.Ldsfld && getter.Body.Instructions[1].OpCode == OpCodes.Ret; })); // Execute copies of the actual patched IL with only its two privileged // I/O calls redirected to fake ports. No hardware instructions execute. using var clone = CreateExecutableFixture(serial); using var bytes = new MemoryStream(); clone.Write(bytes); bytes.Position = 0; var context = new AssemblyLoadContext("BoundedSerialFixture", isCollectible: true); try { var type = context.LoadFromStream(bytes).GetType("Fixture.Serial", throwOnError: true)!; var write = type.GetMethod("ComWrite")!; void Reset(bool enabled, params byte[] statuses) { SerialIoFixture.Reset(enabled, statuses); foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Static)) field.SetValue(null, field.FieldType == typeof(bool) ? false : 0UL); } void Write(byte value) => write.Invoke(null, [value]); ulong Counter(string name) => (ulong)type.GetField("XfeSerialV1_" + name)!.GetValue(null)!; Reset(false, 0); Write(65); check("SerialFeatureDisabledDoesNoPortIo", SerialIoFixture.Reads == 0 && SerialIoFixture.Writes == 0 && Counter("Dropped") == 0); Reset(true, 0x20); Write(65); check("SerialReadyWritesOriginalByteToCom1", SerialIoFixture.Reads == 1 && SerialIoFixture.Writes == 1 && SerialIoFixture.LastValue == 65 && Counter("Written") == 1); Reset(true, 0, 0, 0x20); Write(0xff); check("SerialTransientBusyThenReadyPreservesByte", SerialIoFixture.Reads == 3 && SerialIoFixture.Writes == 1 && SerialIoFixture.LastValue == 0xff && Counter("Timeout") == 0); Reset(true, Enumerable.Repeat((byte)0, 4095).Append((byte)0x20).ToArray()); Write(42); check("SerialLastPermittedPollMayWrite", SerialIoFixture.Reads == 4096 && SerialIoFixture.Writes == 1 && Counter("Timeout") == 0); Reset(true, 0); Write(65); check("SerialStuckZeroReturnsAtExactBudget", SerialIoFixture.Reads == 4096 && SerialIoFixture.Writes == 0 && Counter("Timeout") == 1 && Counter("Dropped") == 1); Write(66); check("SerialStuckOutputStaysDisabledWithoutRepeatingWait", SerialIoFixture.Reads == 4096 && SerialIoFixture.Writes == 0 && Counter("Timeout") == 1 && Counter("Dropped") == 2); Reset(true, 0x10); Write(1); check("SerialStuckNonzeroBusyAlsoReturns", SerialIoFixture.Reads == 4096 && SerialIoFixture.Writes == 0 && Counter("Timeout") == 1); Reset(true, 0xff); Write(65); check("SerialMissingUartReturnsAfterOneRead", SerialIoFixture.Reads == 1 && SerialIoFixture.Writes == 0 && Counter("Absent") == 1 && Counter("Dropped") == 1); Write(66); check("SerialMissingUartStaysDisabled", SerialIoFixture.Reads == 1 && SerialIoFixture.Writes == 0 && Counter("Absent") == 1 && Counter("Dropped") == 2); Reset(true, 0x21); Write(10); Write(13); check("SerialReadyWithOtherStatusBitsAndLineEndings", SerialIoFixture.Reads == 2 && SerialIoFixture.Writes == 2 && SerialIoFixture.LastValue == 13 && Counter("Written") == 2); check("SerialFixturesUseOnlyExistingCom1Ports", SerialIoFixture.InvalidPortAccesses == 0); } finally { context.Unload(); } check("SerialRepeatedApplyVerifiesWithoutMutation", SerialRuntimePatch.Apply(after)); var budget = helper.Body.Instructions.Single(instruction => instruction.OpCode == OpCodes.Ldc_I4 && Equals(instruction.Operand, 4096)); budget.Operand = 4097; check("SerialModifiedPollingBudgetRejected", Rejects(() => SerialRuntimePatch.Apply(after))); budget.Operand = 4096; using var alteredOriginal = AssemblyDefinition.ReadAssembly(new MemoryStream(ToBytes(before))); var changed = alteredOriginal.MainModule.Types.Single(type => type.FullName == serial.FullName).Methods.Single(method => method.Name == "ComWrite"); changed.Body.Instructions.Single(instruction => instruction.OpCode == OpCodes.Ldc_I4 && Equals(instruction.Operand, 0x3fd)).Operand = 0x2fd; check("SerialUnknownOriginalPortShapeRejected", Rejects(() => SerialRuntimePatch.Apply(alteredOriginal))); } private static AssemblyDefinition CreateExecutableFixture(TypeDefinition source) { var fixture = AssemblyDefinition.CreateAssembly(new AssemblyNameDefinition("SerialFixture_" + Guid.NewGuid().ToString("N"), new Version(1, 0)), "SerialFixture", ModuleKind.Dll); var module = fixture.MainModule; var type = new TypeDefinition("Fixture", "Serial", Mono.Cecil.TypeAttributes.Public | Mono.Cecil.TypeAttributes.Abstract | Mono.Cecil.TypeAttributes.Sealed, module.TypeSystem.Object); module.Types.Add(type); var fields = source.Fields.Where(field => field.Name.StartsWith("XfeSerialV1_", StringComparison.Ordinal)).ToDictionary(field => field.Name, field => new FieldDefinition(field.Name, Mono.Cecil.FieldAttributes.Public | Mono.Cecil.FieldAttributes.Static, module.ImportReference(field.FieldType))); foreach (var field in fields.Values) type.Fields.Add(field); var sources = source.Methods.Where(method => method.Name is "ComWrite" or SerialRuntimePatch.HelperName).ToArray(); var methods = sources.ToDictionary(method => method.Name, method => new MethodDefinition(method.Name, Mono.Cecil.MethodAttributes.Public | Mono.Cecil.MethodAttributes.Static, module.ImportReference(method.ReturnType))); foreach (var method in methods.Values) { method.Parameters.Add(new("value", Mono.Cecil.ParameterAttributes.None, module.TypeSystem.Byte)); type.Methods.Add(method); } foreach (var method in sources) { var copy = methods[method.Name]; copy.Body.InitLocals = method.Body.InitLocals; copy.Body.MaxStackSize = method.Body.MaxStackSize; var locals = method.Body.Variables.ToDictionary(local => local, local => new VariableDefinition(module.ImportReference(local.VariableType))); foreach (var local in locals.Values) copy.Body.Variables.Add(local); var instructions = method.Body.Instructions.ToDictionary(instruction => instruction, instruction => Instruction.Create(OpCodes.Nop)); foreach (var instruction in method.Body.Instructions) { var target = instructions[instruction]; target.OpCode = instruction.OpCode; target.Operand = instruction.Operand switch { Instruction branch => instructions[branch], VariableDefinition local => locals[local], FieldReference field => fields[field.Name], MethodReference call when methods.TryGetValue(call.Name, out var own) => own, MethodReference call when call.Name == "get_UARTEnabled" => module.ImportReference(typeof(SerialIoFixture).GetProperty(nameof(SerialIoFixture.UartEnabled))!.GetMethod!), MethodReference call when call.Name == "Read8" => module.ImportReference(typeof(SerialIoFixture).GetMethod(nameof(SerialIoFixture.Read8))!), MethodReference call when call.Name == "Write8" => module.ImportReference(typeof(SerialIoFixture).GetMethod(nameof(SerialIoFixture.Write8))!), MethodReference call => throw new InvalidOperationException("Unexpected serial fixture call: " + call.FullName), var operand => operand }; copy.Body.Instructions.Add(target); } } return fixture; } private static byte[] ToBytes(AssemblyDefinition assembly) { using var stream = new MemoryStream(); assembly.Write(stream); return stream.ToArray(); } private static string Il(MethodDefinition method) => !method.HasBody ? "" : string.Join("\n", method.Body.Instructions.Select(instruction => instruction.ToString())); private static bool Rejects(Action action) { try { action(); return false; } catch (InvalidOperationException) { return true; } } } public static class SerialIoFixture { private static byte[] statuses = [0]; public static bool UartEnabled { get; private set; } public static int Reads { get; private set; } public static int Writes { get; private set; } public static int InvalidPortAccesses { get; private set; } public static byte LastValue { get; private set; } public static void Reset(bool enabled, byte[] values) { UartEnabled = enabled; statuses = values; Reads = Writes = InvalidPortAccesses = 0; LastValue = 0; } public static byte Read8(ushort port) { if (port != 0x3fd) InvalidPortAccesses++; byte value = statuses[Math.Min(Reads, statuses.Length - 1)]; Reads++; return value; } public static void Write8(ushort port, byte value) { if (port != 0x3f8) InvalidPortAccesses++; Writes++; LastValue = value; } }