XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
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;
    }
}