관리되는 .NET 언어로 JIT 컴파일러 (네이티브 코드로)를 작성할 수 있습니까?


84

나는 JIT 컴파일러를 작성하는 아이디어를 가지고 놀면서 관리 코드에서 모든 것을 이론적으로 작성하는 것이 가능한지 궁금합니다. 특히 어셈블러를 바이트 배열로 생성 한 후 실행을 시작하기 위해 어셈블러에 어떻게 뛰어들 수 있습니까?


관리되는 언어에서 때때로 안전하지 않은 컨텍스트에서 작업 할 수 있지만 포인터에서 대리자를 합성 할 수 있다고 생각 하지 않습니다 . 생성 된 코드로 건너 뛰는 방법은 무엇입니까?
Damien_The_Unbeliever

@Damien : 안전하지 않은 코드로 함수 포인터에 쓸 수 없습니까?
Henk Holterman 2012 년

2
"관리되지 않는 코드로 제어를 동적으로 전송하는 방법"과 같은 제목을 사용하면 닫힐 위험이 낮아질 수 있습니다. 요점도 더 많이 보입니다. 코드 생성은 문제가 아닙니다.
Henk Holterman 2012 년

8
가장 간단한 아이디어는 바이트 배열을 파일에 기록하고 OS가 실행하도록하는 것입니다. 결국 인터프리터가 아닌 컴파일러 가 필요합니다 (가능하지만 더 복잡합니다).
Vlad

3
원하는 코드를 JIT로 컴파일 한 후에는 Win32 API를 사용하여 관리되지 않는 메모리 (실행 파일로 표시됨)를 할당하고 컴파일 된 코드를 해당 메모리 공간에 복사 한 다음 IL calliopcode를 사용 하여 컴파일 된 코드를 호출 할 수 있습니다.
Jack P.

답변:


71

그리고 완전한 개념 증명을 위해 여기 에 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

내 찬성에도 불구하고 나는 다른 것을 간청한다. 이것은 JIT가 아니라 임의의 코드 실행이다 . JIT는 "just in time compilation "을 의미 하지만,이 코드 예제에서 "컴파일"측면을 볼 수 없다.
rwong

4
@rwong : "컴파일"측면은 원래 질문 문제의 범위에 없었습니다. 구현의 관리 코드 능력IL-> 네이티브 코드 변환 은 다소 분명합니다.
Gene Belitski 2015

70

그래 넌 할수있어. 사실, 그것은 내 직업입니다 :)

저는 GPU.NET을 완전히 F #으로 작성했습니다 (단위 테스트 모듈로)-실제로 .NET CLR과 마찬가지로 런타임에 IL을 분해하고 JIT합니다. 사용하려는 기본 가속 장치에 대해 네이티브 코드를 내 보냅니다. 현재 우리는 Nvidia GPU 만 지원하지만 최소한의 작업으로 리 타겟팅 할 수 있도록 시스템을 설계 했으므로 향후 다른 플랫폼을 지원할 가능성이 높습니다.

성능에 관해서는 F #이 있습니다. 최적화 된 모드 (꼬리 호출 포함)로 컴파일 할 때 JIT 컴파일러 자체는 아마도 CLR (C ++, IIRC로 작성 됨) 내의 컴파일러만큼 빠릅니다.

실행을 위해, jitted 코드를 실행하기 위해 하드웨어 드라이버에 제어를 전달할 수있는 이점이 있습니다. 그러나 이것은 .NET이 관리되지 않는 / 네이티브 코드에 대한 함수 포인터를 지원하기 때문에 CPU에서 더 어렵지 않을 것입니다 (일반적으로 .NET에서 제공하는 안전 / 보안을 잃게 될 것입니다).


4
NoExecute의 요점은 자신이 직접 만든 코드로 이동할 수 없다는 것이 아닙니까? 함수 포인터를 통해 네이티브 코드로 이동할 수있는 것이 아니라 함수 포인터를 통해 네이티브 코드로 이동할 수 없습니까?
Ian Boyd

비영리 응용 프로그램을 위해 무료로 만들면 여러분이 훨씬 더 많이 노출 될 것이라고 생각합니다. 당신은 "매니아"계층의 엉터리 변화를 잃게 될 것이지만, 그것을 사용하는 더 많은 사람들로부터 노출이 증가한만큼 가치가있을 것입니다 (나는 확실히 할 것임을 압니다)) !
BlueRaja-Danny Pflughoeft

@IanBoyd NoExecute는 대부분 버퍼 오버런 및 관련 문제로 인한 문제를 피하는 또 다른 방법입니다. 자신의 코드에 대한 보호가 아니라 불법 코드 실행을 완화하는 데 도움이됩니다.
Luaan

51

트릭이 있어야 할 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 모두에서 작동).


30

안전하지 않은 코드를 사용하면 델리게이트를 "해킹"하고 생성하여 배열에 저장 한 임의의 어셈블리 코드를 가리킬 수 있습니다. 아이디어는 대리자가 _methodPtr반사를 사용하여 설정할 수 있는 필드 가 있다는 것입니다 . 다음은 몇 가지 샘플 코드입니다.

물론 이것은 .NET 런타임이 변경되면 언제든지 작동을 멈출 수있는 더러운 해킹입니다.

원칙적으로 완전 관리 형 안전 코드는 JIT를 구현할 수 없다고 생각합니다. 이는 런타임이 의존하는 보안 가정을 ​​깨뜨릴 수 있기 때문입니다. (생성 된 어셈블리 코드가 가정을 위반하지 않는다는 기계 검사 가능한 증거와 함께 제공되지 않는 한 ...)


1
좋은 해킹. 링크가 끊어지는 문제를 방지하기 위해 코드의 일부를이 게시물에 복사 할 수 있습니다. (또는이 게시물에 간단한 설명을 작성하십시오).
Felix K.

나는 AccessViolationException당신의 모범을 실행하려고하면 얻을 수 있습니다. DEP가 비활성화 된 경우에만 작동한다고 생각합니다.
Rasmus Faber 2012 년

1
그러나 EXECUTE_READWRITE 플래그로 메모리를 할당하고 _methodPtr 필드에서 사용하면 제대로 작동합니다. Rotor 코드를 살펴보면 기본적으로 Marshal.GetDelegateForFunctionPointer ()가 수행하는 작업 인 것 같습니다. 단, 스택을 설정하고 보안을 처리하기 위해 코드 주변에 추가 썽크가 추가된다는 점이 다릅니다.
Rasmus Faber 2012 년

링크가 죽었다고 생각합니다. 아아, 편집 하겠지만 원본의 재배치를 찾을 수 없습니다.
Abel
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.