using Mono.Cecil;
using Mono.Cecil.Cil;
internal static class EarlyBootConsoleRuntimePatchChecks
{
public static void Run(Action<string, bool> 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<int>();
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; }
}
}
using Mono.Cecil;
using Mono.Cecil.Cil;
internal static class EarlyBootConsoleRuntimePatchChecks
{
public static void Run(Action<string, bool> 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<int>();
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; }
}
}