Я подумываю о написании JIT-компилятора, и мне просто интересно, возможно ли вообще написать все это в управляемом коде. В частности, после того, как вы сгенерировали ассемблер в массив байтов, как перейти к нему, чтобы начать выполнение?
84
calli
код операции IL для вызова скомпилированного кода.Ответы:
И для полного доказательства концепции вот полностью работоспособный перевод подхода Расмуса к 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 # (по модулю наших модульных тестов) - он фактически дизассемблирует и JIT выполняет IL во время выполнения, как это делает .NET CLR. Мы генерируем собственный код для любого базового устройства ускорения, которое вы хотите использовать; в настоящее время мы поддерживаем только графические процессоры Nvidia, но я спроектировал нашу систему так, чтобы ее можно было перенастроить с минимальными усилиями, поэтому, вероятно, мы будем поддерживать другие платформы в будущем.
Что касается производительности, я должен поблагодарить F # - при компиляции в оптимизированном режиме (с хвостовыми вызовами) сам наш JIT-компилятор, вероятно, примерно так же быстр, как компилятор в CLR (который написан на C ++, IIRC).
Что касается исполнения, то у нас есть возможность передать управление аппаратным драйверам для выполнения измененного кода; однако сделать это на ЦП не составит труда, поскольку .NET поддерживает указатели функций на неуправляемый / собственный код (хотя вы потеряете любую безопасность, обычно обеспечиваемую .NET).
источник
Уловкой должен быть VirtualAlloc с
EXECUTE_READWRITE
флагом -flag (требуется 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); }
Полный пример (теперь работает как с X86, так и с X64).
источник
Используя небезопасный код, вы можете «взломать» делегата и заставить его указывать на произвольный ассемблерный код, который вы сгенерировали и сохранили в массиве. Идея в том, что у делегата есть
_methodPtr
поле, которое можно установить с помощью Reflection. Вот пример кода:Это, конечно, грязный прием, который может перестать работать в любой момент при изменении среды выполнения .NET.
Я предполагаю, что в принципе полностью управляемому безопасному коду нельзя позволить реализовать JIT, потому что это нарушит любые предположения о безопасности, на которые полагается среда выполнения. (Если только сгенерированный код сборки не имеет проверяемого машиной доказательства того, что он не нарушает предположения ...)
источник
AccessViolationException
если попытаюсь запустить ваш пример. Я думаю, это работает, только если DEP отключен.