[c#] 관리되는 .NET 언어로 JIT 컴파일러 (네이티브 코드로)를 작성할 수 있습니까?

나는 JIT 컴파일러를 작성하는 아이디어를 가지고 놀면서 관리 코드에서 모든 것을 이론적으로 작성하는 것이 가능한지 궁금합니다. 특히 어셈블러를 바이트 배열로 생성 한 후 실행을 시작하기 위해 어셈블러에 어떻게 뛰어들 수 있습니까?



답변

그리고 완전한 개념 증명을 위해 여기 에 Rasmus의 JIT 접근 방식을 F #으로 완벽하게 변환 할 수 있습니다 .

open System
open System.Runtime.InteropServices

type AllocationType =
    | COMMIT=0x1000u

type MemoryProtection =
    | EXECUTE_READWRITE=0x40u

type FreeType =
    | DECOMMIT = 0x4000u

[<DllImport("kernel32.dll", SetLastError=true)>]
extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, AllocationType flAllocationType, MemoryProtection flProtect);

[<DllImport("kernel32.dll", SetLastError=true)>]
extern bool VirtualFree(IntPtr lpAddress, UIntPtr dwSize, FreeType freeType);

let JITcode: byte[] = [|0x55uy;0x8Buy;0xECuy;0x8Buy;0x45uy;0x08uy;0xD1uy;0xC8uy;0x5Duy;0xC3uy|]

[<UnmanagedFunctionPointer(CallingConvention.Cdecl)>]
type Ret1ArgDelegate = delegate of (uint32) -> uint32

[<EntryPointAttribute>]
let main (args: string[]) =
    let executableMemory = VirtualAlloc(IntPtr.Zero, UIntPtr(uint32(JITcode.Length)), AllocationType.COMMIT, MemoryProtection.EXECUTE_READWRITE)
    Marshal.Copy(JITcode, 0, executableMemory, JITcode.Length)
    let jitedFun = Marshal.GetDelegateForFunctionPointer(executableMemory, typeof<Ret1ArgDelegate>) :?> Ret1ArgDelegate
    let mutable test = 0xFFFFFFFCu
    printfn "Value before: %X" test
    test <- jitedFun.Invoke test
    printfn "Value after: %X" test
    VirtualFree(executableMemory, UIntPtr.Zero, FreeType.DECOMMIT) |> ignore
    0

행복하게 양보를 실행하는

Value before: FFFFFFFC
Value after: 7FFFFFFE


답변

그래 넌 할수있어. 사실, 그것은 내 직업입니다 🙂

저는 GPU.NET을 완전히 F #으로 작성했습니다 (단위 테스트 모듈로)-실제로 .NET CLR과 마찬가지로 런타임에 IL을 분해하고 JIT합니다. 사용하려는 기본 가속 장치에 대해 네이티브 코드를 내 보냅니다. 현재 우리는 Nvidia GPU 만 지원하지만 최소한의 작업으로 리 타겟팅 할 수 있도록 시스템을 설계 했으므로 향후 다른 플랫폼을 지원할 가능성이 높습니다.

성능에 관해서는 F #이 있습니다. 최적화 된 모드 (꼬리 호출 포함)로 컴파일 할 때 JIT 컴파일러 자체는 아마도 CLR (C ++, IIRC로 작성 됨) 내의 컴파일러만큼 빠릅니다.

실행을 위해, jitted 코드를 실행하기 위해 하드웨어 드라이버에 제어를 전달할 수있는 이점이 있습니다. 그러나 이것은 .NET이 관리되지 않는 / 네이티브 코드에 대한 함수 포인터를 지원하기 때문에 CPU에서 더 어렵지 않을 것입니다 (일반적으로 .NET에서 제공하는 안전 / 보안을 잃게 될 것입니다).


답변

트릭이 있어야 할 VirtualAlloc을EXECUTE_READWRITE-flag와 (P / 호출 필요) Marshal.GetDelegateForFunctionPointer는 .

다음은 정수 회전 예제의 수정 된 버전입니다 (여기에는 안전하지 않은 코드가 필요하지 않습니다).

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate uint Ret1ArgDelegate(uint arg1);

public static void Main(string[] args){
    // Bitwise rotate input and return it.
    // The rest is just to handle CDECL calling convention.
    byte[] asmBytes = new byte[]
    {
      0x55,             // push ebp
      0x8B, 0xEC,       // mov ebp, esp 
      0x8B, 0x45, 0x08, // mov eax, [ebp+8]
      0xD1, 0xC8,       // ror eax, 1
      0x5D,             // pop ebp 
      0xC3              // ret
    };

    // Allocate memory with EXECUTE_READWRITE permissions
    IntPtr executableMemory =
        VirtualAlloc(
            IntPtr.Zero,
            (UIntPtr) asmBytes.Length,
            AllocationType.COMMIT,
            MemoryProtection.EXECUTE_READWRITE
        );

    // Copy the machine code into the allocated memory
    Marshal.Copy(asmBytes, 0, executableMemory, asmBytes.Length);

    // Create a delegate to the machine code.
    Ret1ArgDelegate del =
        (Ret1ArgDelegate) Marshal.GetDelegateForFunctionPointer(
            executableMemory,
            typeof(Ret1ArgDelegate)
        );

    // Call it
    uint n = (uint)0xFFFFFFFC;
    n = del(n);
    Console.WriteLine("{0:x}", n);

    // Free the memory
    VirtualFree(executableMemory, UIntPtr.Zero, FreeType.DECOMMIT);
 }

전체 예제 (이제 X86 및 X64 모두에서 작동).


답변

안전하지 않은 코드를 사용하면 델리게이트를 “해킹”하고 생성하여 배열에 저장 한 임의의 어셈블리 코드를 가리킬 수 있습니다. 아이디어는 대리자가 _methodPtr반사를 사용하여 설정할 수 있는 필드 가 있다는 것입니다 . 다음은 몇 가지 샘플 코드입니다.

물론 이것은 .NET 런타임이 변경되면 언제든지 작동을 멈출 수있는 더러운 해킹입니다.

원칙적으로 완전 관리 형 안전 코드는 JIT를 구현할 수 없다고 생각합니다. 이는 런타임이 의존하는 보안 가정을 ​​깨뜨릴 수 있기 때문입니다. (생성 된 어셈블리 코드가 가정을 위반하지 않는다는 기계 검사 가능한 증거와 함께 제공되지 않는 한 …)


답변