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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Buffers.Binary;
using System.Collections.Immutable;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;

namespace XFESystem.ManagedRuntime;

/// <summary>
/// Loads an actual CLI PE image and interprets a deliberately bounded console IL profile.
/// Unknown instructions, exception regions, local calls and native/instance calls are rejected.
/// This does not load the program into the host CLR and does not execute native image bytes.
/// </summary>
public sealed class ManagedApplicationRunner
{
    public ManagedExecutionResult Execute(ReadOnlyMemory<byte> image, IManagedConsole console, int instructionLimit = ManagedRuntimeCapabilities.MaxInstructions)
    {
        ArgumentNullException.ThrowIfNull(console);
        if (image.Length is < 128 or > ManagedRuntimeCapabilities.MaxImageBytes) throw new BadImageFormatException("Managed image size is outside the accepted bounds.");
        if (instructionLimit is < 1 or > ManagedRuntimeCapabilities.MaxInstructions) throw new ArgumentOutOfRangeException(nameof(instructionLimit));
        using var pe = new PEReader(ImmutableArray.Create<byte>(image.ToArray()));
        if (!pe.HasMetadata || pe.PEHeaders.CorHeader is not { } header || (header.Flags & CorFlags.ILOnly) == 0 || (header.Flags & CorFlags.NativeEntryPoint) != 0)
            throw new BadImageFormatException("Expected an IL-only managed PE executable.");
        var metadata = pe.GetMetadataReader();
        if (!metadata.IsAssembly) throw new BadImageFormatException("A netmodule cannot be launched.");
        var assembly = metadata.GetAssemblyDefinition();
        if (!IsNet10(metadata, assembly)) throw new NotSupportedException("This profile accepts net10.0 programs only.");
        var entry = MetadataTokens.EntityHandle(header.EntryPointTokenOrRelativeVirtualAddress);
        if (entry.Kind != HandleKind.MethodDefinition) throw new BadImageFormatException("A managed entry point is required.");
        var method = metadata.GetMethodDefinition((MethodDefinitionHandle)entry);
        if ((method.Attributes & MethodAttributes.Static) == 0 || (method.Attributes & MethodAttributes.PinvokeImpl) != 0)
            throw new NotSupportedException("The entry point must be a static managed method.");
        var entrySignature = metadata.GetBlobReader(method.Signature);
        var entryHeader = entrySignature.ReadSignatureHeader();
        if (entryHeader.IsInstance || entryHeader.IsGeneric) throw new BadImageFormatException("Invalid entry signature.");
        int entryArguments = entrySignature.ReadCompressedInteger();
        var entryReturn = entrySignature.ReadSignatureTypeCode();
        if (entryReturn is not (SignatureTypeCode.Void or SignatureTypeCode.Int32) || entryArguments is < 0 or > 1)
            throw new NotSupportedException("Expected Main() or Main(string[]) returning void or Int32.");
        if (entryArguments == 1 && (entrySignature.ReadSignatureTypeCode() != SignatureTypeCode.SZArray || entrySignature.ReadSignatureTypeCode() != SignatureTypeCode.String))
            throw new NotSupportedException("Main arguments must be string[].");
        if (entrySignature.RemainingBytes != 0) throw new BadImageFormatException("Trailing entry signature bytes.");
        var body = pe.GetMethodBody(method.RelativeVirtualAddress);
        if (body.ExceptionRegions.Length != 0) throw new NotSupportedException("Exception-handling IL is outside this initial compatibility profile.");
        var code = body.GetILBytes() ?? throw new BadImageFormatException("Missing method body.");
        ValidateInstructions(code);
        var stack = new object?[Math.Min(body.MaxStack, 256)]; int count = 0, pc = 0, executed = 0, calls = 0;
        if (body.MaxStack > 256) throw new NotSupportedException("Evaluation stack limit exceeded.");
        var locals = new object?[LocalCount(metadata, body.LocalSignature)];
        int stringCharacters = 0;
        void ChargeString(int length)
        {
            if (length > ManagedRuntimeCapabilities.MaxStringCharacters || length > ManagedRuntimeCapabilities.MaxTotalStringCharacters - stringCharacters)
                throw new InvalidOperationException("Managed string allocation limit exceeded.");
            stringCharacters += length;
        }
        object? Pop() { if (count == 0) throw new BadImageFormatException("Evaluation stack underflow."); var value = stack[--count]; stack[count] = null; return value; }
        void Push(object? value) { if (count >= stack.Length) throw new BadImageFormatException("Evaluation stack overflow."); stack[count++] = value; }
        int Number(object? value) => value is int number ? number : throw new NotSupportedException("Expected an Int32 stack value.");
        while (pc < code.Length)
        {
            if (++executed > instructionLimit) throw new InvalidOperationException("Managed instruction limit exceeded.");
            byte op = code[pc++];
            if (op == 0x00) continue; // nop
            if (op == 0x02) { if (entryArguments != 1) throw new BadImageFormatException("No argument exists."); Push(Array.Empty<string>()); continue; }
            if (op is >= 0x06 and <= 0x09) { Push(Local(locals, op - 0x06)); continue; }
            if (op is >= 0x0A and <= 0x0D) { SetLocal(locals, op - 0x0A, Pop()); continue; }
            if (op == 0x11) { Push(Local(locals, code[pc++])); continue; }
            if (op == 0x13) { SetLocal(locals, code[pc++], Pop()); continue; }
            if (op == 0x14) { Push(null); continue; }
            if (op is >= 0x15 and <= 0x1E) { Push(op - 0x16); continue; }
            if (op == 0x1F) { Push((int)(sbyte)code[pc++]); continue; }
            if (op == 0x20) { Push(ReadInt(code, ref pc)); continue; }
            if (op == 0x25) { var value = Pop(); Push(value); Push(value); continue; }
            if (op == 0x26) { Pop(); continue; }
            if (op == 0x72)
            {
                int token = ReadInt(code, ref pc);
                if ((token & unchecked((int)0xFF000000)) != 0x70000000) throw new BadImageFormatException("Invalid user string token.");
                var value = metadata.GetUserString(MetadataTokens.UserStringHandle(token & 0xFFFFFF));
                if (value.Length > 16_384) throw new NotSupportedException("Managed string literal limit exceeded.");
                ChargeString(value.Length);
                Push(value); continue;
            }
            if (op == 0x28) { int token = ReadInt(code, ref pc); Call(metadata, token, console, Pop, Push, ChargeString); calls++; continue; }
            if (op == 0x2A)
            {
                if (count != (entryReturn == SignatureTypeCode.Void ? 0 : 1)) throw new BadImageFormatException("Unexpected stack at ret.");
                return new(metadata.GetString(assembly.Name), executed, entryReturn == SignatureTypeCode.Void ? 0 : Number(Pop()), calls);
            }
            if (op is >= 0x58 and <= 0x5A)
            {
                int right = Number(Pop()), left = Number(Pop());
                Push(op == 0x58 ? unchecked(left + right) : op == 0x59 ? unchecked(left - right) : unchecked(left * right)); continue;
            }
            if (op is >= 0x2B and <= 0x33 || op is >= 0x38 and <= 0x40)
            {
                bool shortForm = op < 0x38;
                int kind = shortForm ? op - 0x2B : op - 0x38;
                int offset = shortForm ? (sbyte)code[pc++] : ReadInt(code, ref pc);
                bool jump;
                if (kind == 0) jump = true;
                else if (kind is 1 or 2) { var value = Pop(); bool truth = value is not null && (value is not int number || number != 0); jump = kind == 2 ? truth : !truth; }
                else
                {
                    int right = Number(Pop()), left = Number(Pop());
                    jump = kind switch { 3 => left == right, 4 => left >= right, 5 => left > right, 6 => left <= right, 7 => left < right, 8 => left != right, _ => false };
                }
                if (jump) pc += offset;
                continue;
            }
            throw new NotSupportedException($"Unsupported IL opcode 0x{op:X2}.");
        }
        throw new BadImageFormatException("Method fell through without ret.");
    }

    private static void Call(MetadataReader reader, int token, IManagedConsole console, Func<object?> pop, Action<object?> push, Action<int> chargeString)
    {
        var handle = MetadataTokens.EntityHandle(token);
        if (handle.Kind != HandleKind.MemberReference) throw new NotSupportedException("Only declared platform interop calls are available.");
        var member = reader.GetMemberReference((MemberReferenceHandle)handle);
        if (member.Parent.Kind != HandleKind.TypeReference) throw new NotSupportedException("Unsupported call owner.");
        var type = reader.GetTypeReference((TypeReferenceHandle)member.Parent);
        string owner = reader.GetString(type.Namespace) + "." + reader.GetString(type.Name), name = reader.GetString(member.Name);
        if (type.ResolutionScope.Kind != HandleKind.AssemblyReference) throw new NotSupportedException("Only external platform bindings are permitted.");
        string scope = reader.GetString(reader.GetAssemblyReference((AssemblyReferenceHandle)type.ResolutionScope).Name);
        var signature = reader.GetBlobReader(member.Signature); var header = signature.ReadSignatureHeader();
        if (header.Kind != SignatureKind.Method || header.IsInstance || header.IsGeneric) throw new NotSupportedException("Instance/generic interop is unavailable.");
        int parameterCount = signature.ReadCompressedInteger();
        if (parameterCount is < 0 or > 4) throw new NotSupportedException("Interop parameter limit exceeded.");
        var returnType = SignatureType(reader, ref signature);
        var parameterTypes = new string[parameterCount];
        for (int i = 0; i < parameterCount; i++) parameterTypes[i] = SignatureType(reader, ref signature);
        if (signature.RemainingBytes != 0) throw new BadImageFormatException("Trailing interop signature bytes.");
        var arguments = new object?[parameterCount];
        for (int i = parameterCount - 1; i >= 0; i--) arguments[i] = pop();
        for (int i = 0; i < parameterCount; i++)
            if (parameterTypes[i] == "String" ? arguments[i] is not (null or string) : arguments[i] is not int)
                throw new BadImageFormatException("Evaluation stack argument type does not match the interop signature.");
        string Text(int i) => arguments[i] switch { null => "", string text => text, int value => value.ToString(), _ => throw new NotSupportedException("Unsupported console parameter type.") };
        if (owner == "System.Console" && scope == "System.Console")
        {
            if (name == "WriteLine" && parameterCount == 0 && returnType == "Void") { console.WriteLine(""); return; }
            if (name is "WriteLine" or "Write" && parameterCount == 1 && returnType == "Void" && parameterTypes[0] is "String" or "Int32")
            { if (name == "WriteLine") console.WriteLine(Text(0)); else console.Write(Text(0)); return; }
            if (name == "ResetColor" && parameterCount == 0 && returnType == "Void") { console.ResetColor(); return; }
            if (name == "set_ForegroundColor" && parameterCount == 1 && returnType == "Void" && parameterTypes[0] == "System.ConsoleColor" && arguments[0] is int fg) { console.ForegroundColor = (ConsoleColor)fg; return; }
            if (name == "set_BackgroundColor" && parameterCount == 1 && returnType == "Void" && parameterTypes[0] == "System.ConsoleColor" && arguments[0] is int bg) { console.BackgroundColor = (ConsoleColor)bg; return; }
            if (name == "get_ForegroundColor" && parameterCount == 0 && returnType == "System.ConsoleColor") { push((int)console.ForegroundColor); return; }
            if (name == "get_BackgroundColor" && parameterCount == 0 && returnType == "System.ConsoleColor") { push((int)console.BackgroundColor); return; }
        }
        if (owner == "XFEExtension.NetCore.XFEConsole.XFEConsole" && scope == "XFEExtension.NetCore.XFEConsole" && name == "WriteLine" && parameterCount == 1 && parameterTypes[0] == "String" && returnType == "Void")
        { console.XfeWriteLine(Text(0)); return; }
        if (owner == "System.String" && scope == "System.Runtime" && name == "Concat" && parameterCount == 2 && returnType == "String" && parameterTypes[0] == "String" && parameterTypes[1] == "String")
        { string left = Text(0), right = Text(1); chargeString(checked(left.Length + right.Length)); push(left + right); return; }
        throw new NotSupportedException($"Platform binding unavailable: {owner}.{name}");
    }

    private static string SignatureType(MetadataReader reader, ref BlobReader signature)
    {
        var code = signature.ReadSignatureTypeCode();
        if (code == SignatureTypeCode.TypeHandle)
        {
            var handle = signature.ReadTypeHandle();
            if (handle.Kind != HandleKind.TypeReference) throw new NotSupportedException("Only external primitive enum signatures are accepted.");
            var type = reader.GetTypeReference((TypeReferenceHandle)handle);
            if (type.ResolutionScope.Kind != HandleKind.AssemblyReference || reader.GetString(reader.GetAssemblyReference((AssemblyReferenceHandle)type.ResolutionScope).Name) != "System.Console")
                throw new NotSupportedException("Unsupported enum assembly.");
            return reader.GetString(type.Namespace) + "." + reader.GetString(type.Name);
        }
        return code switch
        {
            SignatureTypeCode.Void => "Void",
            SignatureTypeCode.String => "String",
            SignatureTypeCode.Int32 => "Int32",
            _ => throw new NotSupportedException("Unsupported platform signature type.")
        };
    }

    private static bool IsNet10(MetadataReader reader, AssemblyDefinition assembly)
    {
        foreach (var handle in assembly.GetCustomAttributes())
        {
            var attribute = reader.GetCustomAttribute(handle);
            if (attribute.Constructor.Kind != HandleKind.MemberReference) continue;
            var constructor = reader.GetMemberReference((MemberReferenceHandle)attribute.Constructor);
            if (constructor.Parent.Kind != HandleKind.TypeReference) continue;
            var type = reader.GetTypeReference((TypeReferenceHandle)constructor.Parent);
            if (reader.GetString(type.Namespace) != "System.Runtime.Versioning" || reader.GetString(type.Name) != "TargetFrameworkAttribute") continue;
            var value = reader.GetBlobReader(attribute.Value);
            return value.ReadUInt16() == 1 && value.ReadSerializedString() == ManagedRuntimeCapabilities.TargetFramework;
        }
        return false;
    }
    private static int LocalCount(MetadataReader reader, StandaloneSignatureHandle handle)
    {
        if (handle.IsNil) return 0;
        var signature = reader.GetBlobReader(reader.GetStandaloneSignature(handle).Signature);
        if (signature.ReadSignatureHeader().Kind != SignatureKind.LocalVariables) throw new BadImageFormatException("Invalid local signature.");
        int count = signature.ReadCompressedInteger();
        if (count is < 0 or > 64) throw new NotSupportedException("Local variable limit exceeded.");
        for (int i = 0; i < count; i++)
            if (signature.ReadSignatureTypeCode() is not (SignatureTypeCode.Int32 or SignatureTypeCode.Boolean))
                throw new NotSupportedException("This initial profile supports Int32/Boolean locals only.");
        if (signature.RemainingBytes != 0) throw new BadImageFormatException("Trailing local signature bytes.");
        return count;
    }
    private static object? Local(object?[] locals, int index) => index < locals.Length ? locals[index] ?? 0 : throw new BadImageFormatException("Invalid local index.");
    private static void SetLocal(object?[] locals, int index, object? value)
    { if (index >= locals.Length) throw new BadImageFormatException("Invalid local index."); locals[index] = value; }
    private static int ReadInt(byte[] code, ref int at) { int value = BinaryPrimitives.ReadInt32LittleEndian(code.AsSpan(at, 4)); at += 4; return value; }
    private static void ValidateInstructions(byte[] code)
    {
        if (code.Length > 65_536) throw new NotSupportedException("Method body size limit exceeded.");
        var boundaries = new HashSet<int>(); var targets = new List<int>();
        int at = 0;
        while (at < code.Length)
        {
            boundaries.Add(at); byte op = code[at++];
            int length = op switch
            {
                0x00 or 0x02 or >= 0x06 and <= 0x0D or >= 0x14 and <= 0x1E or 0x25 or 0x26 or 0x2A or >= 0x58 and <= 0x5A => 0,
                0x11 or 0x13 or 0x1F or >= 0x2B and <= 0x33 => 1,
                0x20 or 0x28 or 0x72 or >= 0x38 and <= 0x40 => 4,
                _ => throw new NotSupportedException($"Unsupported IL opcode 0x{op:X2}.")
            };
            if (code.Length - at < length) throw new BadImageFormatException("Truncated IL operand.");
            if (op is >= 0x2B and <= 0x33) targets.Add(at + 1 + (sbyte)code[at]);
            if (op is >= 0x38 and <= 0x40) targets.Add(at + 4 + BinaryPrimitives.ReadInt32LittleEndian(code.AsSpan(at, 4)));
            at += length;
        }
        foreach (int target in targets) if (!boundaries.Contains(target)) throw new BadImageFormatException("Branch target is outside an instruction boundary.");
    }
}