我正在考虑编写一个 JIT 编译器的想法,我只是想知道在理论上是否有可能用托管代码编写整个过程。特别是,一旦你生成了一个字节数组的汇编程序,你如何跳转到它开始执行?
使用不安全的代码,您可以“黑客”一个委托,并使其指向您生成并存储在数组中的任意汇编代码。其思想是,委托有一个 _methodPtr字段,可以使用反射设置该字段。下面是一些示例代码:
_methodPtr
当然,这是一个肮脏的黑客行为,当.NET 运行时发生变化时,它可能随时停止工作。
我猜,原则上不允许完全托管的安全代码实现 JIT,因为这会打破运行时所依赖的任何安全假设。(除非,生成的汇编代码附带了一个机器可检查的证据,证明它没有违反假设... ...)
是的,你可以。事实上,这是我的工作:)
我完全用 F # (模块化我们的单元测试)编写了 GPU.NET ——它实际上在运行时反汇编和 JITs IL,就像。NET CLR 做。我们为任何你想要使用的底层加速设备发布本地代码; 目前我们只支持 Nvidia GPU,但是我已经设计了我们的系统,以最小的工作量重定向,所以我们很可能在未来支持其他平台。
至于性能,我要感谢 F # ——当以优化模式编译时(使用尾调用) ,我们的 JIT 编译器本身可能与 CLR 中的编译器(用 C + + ,IIRC 编写)一样快。
对于执行来说,我们可以将控制权传递给硬件驱动程序来运行抖动的代码; 然而,在 CPU 上这样做并不难。NET 支持指向非托管/本机代码的函数指针(尽管您会失去通常由。NET).
诀窍应该是 VirtualAlloc与 EXECUTE_READWRITE-标志(需要 P/调用)和 函数指针。
EXECUTE_READWRITE
下面是旋转整数示例的修改版本(注意,这里不需要不安全的代码) :
[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)。
为了充分证明这个概念,这里有一个将 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