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<string, bool> 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;
}
}
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<string, bool> 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;
}
}