using Mono.Cecil; using Mono.Cecil.Cil; internal static class EarlyBootConsoleRuntimePatchChecks { public static void Run(Action check) { using var assembly = Fixture(); var console = assembly.MainModule.Types.Single(type => type.Name == "EarlyGop"); var originalPutChar = console.Methods.Single(method => method.Name == "PutChar"); var originalBody = originalPutChar.Body.Instructions.ToArray(); check("EarlyConsoleFirstApplyAddsExports", !EarlyBootConsoleRuntimePatch.Apply(assembly)); check("EarlyConsoleSecondApplyVerifiesWithoutDuplicates", EarlyBootConsoleRuntimePatch.Apply(assembly) && console.Methods.Count == 3); check("EarlyConsoleOriginalWriterUnchanged", originalBody.SequenceEqual(originalPutChar.Body.Instructions)); var writer = console.Methods.Single(method => method.Name == EarlyBootConsoleRuntimePatch.WriteCharName); int[] inputs = [int.MinValue, -65526, -1, 0, 9, 10, 13, 31, 32, 65, 126, 127, 255, 65546, int.MaxValue]; check("EarlyConsoleAsciiAndLineBreaksOnly", inputs.All(value => Simulate(writer, value) == (value is '\n' or '\r' or >= 0x20 and <= 0x7e ? value : '?'))); check("EarlyConsoleExportsAvoidAllocationAndExceptions", console.Methods.Where(method => method.Name != "PutChar").All(method => method.Body.Variables.Count == 0 && method.Body.ExceptionHandlers.Count == 0 && method.Body.Instructions.All(instruction => instruction.OpCode.Code is not (Code.Newobj or Code.Newarr or Code.Throw or Code.Callvirt)))); using (var serialized = new MemoryStream()) { assembly.Write(serialized); serialized.Position = 0; using var reloaded = AssemblyDefinition.ReadAssembly(serialized); check("EarlyConsoleRoundtripIdempotence", EarlyBootConsoleRuntimePatch.Apply(reloaded)); } using (var partial = Fixture()) { EarlyBootConsoleRuntimePatch.Apply(partial); var type = partial.MainModule.Types.Single(type => type.Name == "EarlyGop"); type.Methods.Remove(type.Methods.Single(method => method.Name == EarlyBootConsoleRuntimePatch.WriteCharName)); check("EarlyConsolePartialPatchRejected", Rejects(() => EarlyBootConsoleRuntimePatch.Apply(partial))); } using (var altered = Fixture()) { EarlyBootConsoleRuntimePatch.Apply(altered); var type = altered.MainModule.Types.Single(type => type.Name == "EarlyGop"); type.Methods.Single(method => method.Name == EarlyBootConsoleRuntimePatch.WriteCharName).Body.Instructions[10].Operand = 255; check("EarlyConsoleAlteredRangeRejected", Rejects(() => EarlyBootConsoleRuntimePatch.Apply(altered))); } using (var altered = Fixture()) { var type = altered.MainModule.Types.Single(type => type.Name == "EarlyGop"); type.Fields.Single().FieldType = altered.MainModule.TypeSystem.Int32; check("EarlyConsoleChangedFieldContractRejected", Rejects(() => EarlyBootConsoleRuntimePatch.Apply(altered))); } using (var altered = Fixture()) { var type = altered.MainModule.Types.Single(type => type.Name == "EarlyGop"); type.Methods.Single().Parameters[0].ParameterType = altered.MainModule.TypeSystem.Int32; check("EarlyConsoleChangedWriterContractRejected", Rejects(() => EarlyBootConsoleRuntimePatch.Apply(altered))); } using (var altered = Fixture()) { EarlyBootConsoleRuntimePatch.Apply(altered); var type = altered.MainModule.Types.Single(type => type.Name == "EarlyGop"); var method = type.Methods.Single(method => method.Name == EarlyBootConsoleRuntimePatch.WriteCharName); method.Body.Instructions[2].Operand = method.Body.Instructions[^1]; check("EarlyConsoleAlteredBranchRejected", Rejects(() => EarlyBootConsoleRuntimePatch.Apply(altered))); } } private static AssemblyDefinition Fixture() { var assembly = AssemblyDefinition.CreateAssembly(new AssemblyNameDefinition("EarlyConsoleFixture", new Version(1, 0)), "EarlyConsoleFixture", ModuleKind.Dll); var module = assembly.MainModule; var type = new TypeDefinition("Cosmos.Kernel.Core.IO", "EarlyGop", TypeAttributes.NotPublic | TypeAttributes.Abstract | TypeAttributes.Sealed, module.TypeSystem.Object); module.Types.Add(type); var enabled = new FieldDefinition("Enabled", FieldAttributes.Public | FieldAttributes.Static, module.TypeSystem.Boolean); type.Fields.Add(enabled); var putChar = new MethodDefinition("PutChar", MethodAttributes.Public | MethodAttributes.Static, module.TypeSystem.Void); putChar.Parameters.Add(new("character", ParameterAttributes.None, module.TypeSystem.Char)); type.Methods.Add(putChar); var il = putChar.Body.GetILProcessor(); il.Append(il.Create(OpCodes.Ldsfld, enabled)); il.Append(il.Create(OpCodes.Pop)); il.Append(il.Create(OpCodes.Ret)); return assembly; } // Interpret the small integer/branch prefix rather than execute a hardware // framebuffer writer on the host. The output is the argument to PutChar. private static int Simulate(MethodDefinition method, int value) { var stack = new Stack(); var instructions = method.Body.Instructions; for (int index = 0, budget = 50; budget-- > 0; index++) { var instruction = instructions[index]; switch (instruction.OpCode.Code) { case Code.Ldarg_0: stack.Push(value); break; case Code.Ldc_I4: stack.Push((int)instruction.Operand); break; case Code.Beq: case Code.Blt_Un: case Code.Bgt_Un: int right = stack.Pop(), left = stack.Pop(); bool taken = instruction.OpCode.Code switch { Code.Beq => left == right, Code.Blt_Un => unchecked((uint)left) < unchecked((uint)right), _ => unchecked((uint)left) > unchecked((uint)right) }; if (taken) index = instructions.IndexOf((Instruction)instruction.Operand) - 1; break; case Code.Br: index = instructions.IndexOf((Instruction)instruction.Operand) - 1; break; case Code.Conv_U2: stack.Push(unchecked((ushort)stack.Pop())); break; case Code.Call: return stack.Pop(); default: throw new InvalidOperationException("Unexpected early console simulation instruction."); } } throw new InvalidOperationException("The early console simulation exceeded its bounded instruction budget."); } private static bool Rejects(Action action) { try { action(); return false; } catch (InvalidOperationException) { return true; } } }