Je joue avec l'idée d'écrire un compilateur JIT et je me demande simplement s'il est même théoriquement possible d'écrire le tout en code managé. En particulier, une fois que vous avez généré l'assembleur dans un tableau d'octets, comment y sauter pour commencer l'exécution?
84
calli
opcode IL pour appeler le code compilé.Réponses:
Et pour la preuve de concept complète, voici une traduction entièrement capable de l'approche de Rasmus à JIT en 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
qui exécute joyeusement céder
Value before: FFFFFFFC Value after: 7FFFFFFE
la source
Oui, vous pouvez. En fait, c'est mon travail :)
J'ai écrit GPU.NET entièrement en F # (modulo nos tests unitaires) - il désassemble en fait et JITs IL au moment de l'exécution, tout comme le .NET CLR le fait. Nous émettons du code natif pour tout périphérique d'accélération sous-jacent que vous souhaitez utiliser; actuellement, nous ne prenons en charge que les GPU Nvidia, mais j'ai conçu notre système pour être reciblable avec un minimum de travail, il est donc probable que nous prendrons en charge d'autres plates-formes à l'avenir.
En ce qui concerne les performances, je dois remercier F # - lorsqu'il est compilé en mode optimisé (avec des appels arrière), notre compilateur JIT lui-même est probablement à peu près aussi rapide que le compilateur dans le CLR (qui est écrit en C ++, IIRC).
Pour l'exécution, nous avons l'avantage de pouvoir passer le contrôle aux pilotes matériels pour exécuter le code jitted; cependant, ce ne serait pas plus difficile à faire sur le processeur car .NET prend en charge les pointeurs de fonction vers le code non managé / natif (bien que vous perdriez toute sécurité normalement fournie par .NET).
la source
L'astuce devrait être VirtualAlloc avec le
EXECUTE_READWRITE
-flag (nécessite P / Invoke) et Marshal.GetDelegateForFunctionPointer .Voici une version modifiée de l'exemple de rotation d'entier (notez qu'aucun code non sécurisé n'est nécessaire ici):
[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); }
Exemple complet (fonctionne désormais avec X86 et X64).
la source
En utilisant du code non sécurisé, vous pouvez «pirater» un délégué et le faire pointer vers un code d'assembly arbitraire que vous avez généré et stocké dans un tableau. L'idée est que le délégué a un
_methodPtr
champ, qui peut être défini à l'aide de Reflection. Voici un exemple de code:C'est, bien sûr, un hack sale qui peut cesser de fonctionner à tout moment lorsque le runtime .NET change.
Je suppose qu'en principe, un code sécurisé entièrement géré ne peut pas être autorisé à implémenter JIT, car cela briserait toutes les hypothèses de sécurité sur lesquelles repose l'exécution. (Sauf si le code d'assemblage généré est livré avec une preuve vérifiable par machine qu'il ne viole pas les hypothèses ...)
la source
AccessViolationException
si j'essaye d'exécuter votre exemple. Je suppose que cela ne fonctionne que si DEP est désactivé.