나는 JIT 컴파일러를 작성하는 아이디어를 가지고 놀면서 관리 코드에서 모든 것을 이론적으로 작성하는 것이 가능한지 궁금합니다. 특히 어셈블러를 바이트 배열로 생성 한 후 실행을 시작하기 위해 어셈블러에 어떻게 뛰어들 수 있습니까?
calli
opcode를 사용 하여 컴파일 된 코드를 호출 할 수 있습니다.
나는 JIT 컴파일러를 작성하는 아이디어를 가지고 놀면서 관리 코드에서 모든 것을 이론적으로 작성하는 것이 가능한지 궁금합니다. 특히 어셈블러를 바이트 배열로 생성 한 후 실행을 시작하기 위해 어셈블러에 어떻게 뛰어들 수 있습니까?
calli
opcode를 사용 하여 컴파일 된 코드를 호출 할 수 있습니다.
답변:
그리고 완전한 개념 증명을 위해 여기 에 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를 구현할 수 없다고 생각합니다. 이는 런타임이 의존하는 보안 가정을 깨뜨릴 수 있기 때문입니다. (생성 된 어셈블리 코드가 가정을 위반하지 않는다는 기계 검사 가능한 증거와 함께 제공되지 않는 한 ...)
AccessViolationException
당신의 모범을 실행하려고하면 얻을 수 있습니다. DEP가 비활성화 된 경우에만 작동한다고 생각합니다.