我正在考虑编写JIT编译器的想法,我只是想知道在理论上是否可以在托管代码中编写整个内容。特别是,一旦你将汇编程序生成为字节数组,你如何跳转到它开始执行?
答案 0 :(得分:70)
是的,你可以。事实上,这是我的工作:)
我完全用F#编写GPU.NET(模块化我们的单元测试) - 它实际上在运行时反汇编和JIT IL,就像.NET CLR一样。我们为您想要使用的任何底层加速设备发出本机代码;目前我们只支持Nvidia GPU,但我设计的系统可以通过最少的工作进行重定向,因此我们很可能在将来支持其他平台。
至于性能,感谢F# - 当在优化模式下编译(使用tailcalls)时,我们的JIT编译器本身可能与CLR中的编译器(用C ++,IIRC编写)一样快。 / p>
为了执行,我们的好处是能够将控制权交给硬件驱动程序来运行jitted代码;但是,这不会对CPU造成任何困难,因为.NET支持非托管/本机代码的函数指针(尽管您将失去.NET通常提供的任何安全/安全性)。
答案 1 :(得分:70)
对于完整的概念证明,这是一个完全能力将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
答案 2 :(得分:50)
诀窍应该是VirtualAlloc EXECUTE_READWRITE
- 标志(需要P / Invoke)和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);
}
Full example(现在适用于X86和X64)。
答案 3 :(得分:30)
使用不安全的代码,您可以“破解”委托并使其指向您生成并存储在数组中的任意汇编代码。这个想法是委托有一个_methodPtr
字段,可以使用Reflection设置。以下是一些示例代码:
当然,这是一个脏的黑客攻击,可能会在.NET运行时更改时随时停止工作。
我想原则上,完全托管的安全代码不能被允许实现JIT,因为这会破坏运行时所依赖的任何安全性假设。 (除非,生成的汇编代码带有机器可检查证明它不违反假设......)