Có thể viết trình biên dịch JIT (sang mã gốc) hoàn toàn bằng ngôn ngữ .NET được quản lý không


84

Tôi đang đùa giỡn với ý tưởng viết một trình biên dịch JIT và tôi chỉ đang tự hỏi liệu về mặt lý thuyết có thể viết toàn bộ trong mã được quản lý hay không. Đặc biệt, khi bạn đã tạo trình hợp dịch thành một mảng byte, làm cách nào để bạn chuyển vào đó để bắt đầu thực thi?


Tôi không tin là có - trong khi đôi khi bạn có thể làm việc trong bối cảnh không an toàn bằng các ngôn ngữ được quản lý, tôi không tin rằng bạn có thể tổng hợp một đại biểu từ một con trỏ - và làm thế nào khác bạn sẽ chuyển đến mã đã tạo?
Damien_The_Un Believer

@Damien: mã không an toàn có cho phép bạn ghi vào con trỏ hàm không?
Henk Holterman

2
Với tiêu đề như "cách tự động chuyển quyền kiểm soát sang mã không được quản lý", bạn có thể có nguy cơ bị đóng cửa thấp hơn. Nó có vẻ nhiều hơn vào điểm quá. Tạo mã không phải là vấn đề.
Henk Holterman

8
Ý tưởng đơn giản nhất là ghi mảng byte vào một tệp và để hệ điều hành chạy nó. Sau cùng, bạn cần một trình biên dịch , không phải thông dịch viên (điều này cũng có thể xảy ra nhưng phức tạp hơn).
Vlad

3
Khi bạn đã biên dịch mã JIT mà bạn muốn, bạn có thể sử dụng các API Win32 để phân bổ một số bộ nhớ không được quản lý (được đánh dấu là có thể thực thi), sao chép mã đã biên dịch vào không gian bộ nhớ đó, sau đó sử dụng IL calliopcode để gọi mã đã biên dịch.
Jack P.

Câu trả lời:


71

Và để có bằng chứng đầy đủ về khái niệm ở đây là bản dịch đầy đủ khả năng của phương pháp tiếp cận JIT của Rasmus thành 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

điều đó vui vẻ thực hiện mang lại

Value before: FFFFFFFC
Value after: 7FFFFFFE

Bất chấp sự ủng hộ của tôi, tôi xin phép khác: đây là thực thi mã tùy ý , không phải JIT - JIT có nghĩa là " biên dịch đúng lúc ", nhưng tôi không thể thấy khía cạnh "biên dịch" từ ví dụ mã này.
rwong

4
@rwong: khía cạnh "biên dịch" chưa bao giờ nằm ​​trong phạm vi của các câu hỏi ban đầu. Khả năng mã được quản lý thực hiện IL -> chuyển đổi mã gốc là rõ ràng.
Gene Belitski

70

Có, bạn có thể. Trên thực tế, đó là công việc của tôi :)

Tôi đã viết GPU.NET hoàn toàn bằng F # (modulo các bài kiểm tra đơn vị của chúng tôi) - nó thực sự tháo rời và JITs IL tại thời điểm chạy, giống như .NET CLR. Chúng tôi phát ra mã gốc cho bất kỳ thiết bị tăng tốc cơ bản nào bạn muốn sử dụng; hiện tại chúng tôi chỉ hỗ trợ GPU Nvidia, nhưng tôi đã thiết kế hệ thống của mình có thể nhắm mục tiêu lại với mức công việc tối thiểu nên có khả năng chúng tôi sẽ hỗ trợ các nền tảng khác trong tương lai.

Đối với hiệu suất, tôi có F # để cảm ơn - khi được biên dịch ở chế độ tối ưu hóa (với các cuộc gọi riêng), bản thân trình biên dịch JIT của chúng tôi có thể nhanh ngang với trình biên dịch trong CLR (được viết bằng C ++, IIRC).

Để thực thi, chúng tôi có lợi ích là có thể chuyển quyền kiểm soát cho trình điều khiển phần cứng để chạy mã jitted; tuy nhiên, điều này sẽ không khó thực hiện hơn trên CPU vì .NET hỗ trợ con trỏ chức năng đến mã không được quản lý / gốc (mặc dù bạn sẽ mất mọi an toàn / bảo mật thường được cung cấp bởi .NET).


4
Không phải toàn bộ điểm của NoExecute là bạn không thể chuyển đến mã mà bạn đã tự tạo? Thay vì có thể chuyển đến mã gốc thông qua con trỏ hàm: không phải là không thể chuyển đến mã gốc thông qua con trỏ hàm?
Ian Boyd

Dự án tuyệt vời, mặc dù tôi nghĩ các bạn sẽ nhận được nhiều quảng cáo hơn nếu bạn làm cho nó miễn phí cho các ứng dụng phi lợi nhuận. Bạn sẽ mất sự thay đổi chump so với cấp độ "người đam mê", nhưng nó sẽ rất xứng đáng cho việc tăng mức độ hiển thị từ nhiều người hơn sử dụng nó (tôi biết tôi chắc chắn sẽ làm thế;)) !
BlueRaja - Danny Pflughoeft

@IanBoyd NoExecute chủ yếu là một cách khác để tránh rắc rối do ghi đè bộ đệm và các vấn đề liên quan. Nó không phải là biện pháp bảo vệ khỏi mã của riêng bạn mà là thứ để giúp giảm thiểu việc thực thi mã bất hợp pháp.
Luaan

51

Thủ thuật nên là VirtualAlloc với EXECUTE_READWRITE-flag (cần P / Invoke) và Marshal.GetDelegateForFunctionPointer .

Đây là phiên bản đã sửa đổi của ví dụ số nguyên xoay (lưu ý rằng không cần mã không an toàn ở đây):

[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);
 }

Ví dụ đầy đủ (hiện hoạt động với cả X86 và X64).


30

Sử dụng mã không an toàn, bạn có thể "hack" một đại biểu và làm cho nó trỏ đến một mã lắp ráp tùy ý mà bạn đã tạo và lưu trữ trong một mảng. Ý tưởng là đại biểu có một _methodPtrtrường, có thể được đặt bằng cách sử dụng Phản chiếu. Đây là một số mã mẫu:

Tất nhiên, đây là một bản hack bẩn có thể ngừng hoạt động bất cứ lúc nào khi thời gian chạy .NET thay đổi.

Tôi đoán rằng, về nguyên tắc, mã an toàn được quản lý đầy đủ không thể được phép triển khai JIT, bởi vì điều đó sẽ phá vỡ mọi giả định bảo mật mà thời gian chạy dựa vào. (Trừ khi, mã lắp ráp được tạo đi kèm với bằng chứng có thể kiểm tra bằng máy rằng nó không vi phạm các giả định ...)


1
Hack đẹp. Có thể bạn có thể sao chép một số phần của mã vào bài đăng này để tránh các vấn đề sau này với các liên kết bị hỏng. (Hoặc chỉ cần viết một mô tả nhỏ vào bài đăng này).
Felix K.

Tôi nhận được một AccessViolationExceptionnếu tôi cố gắng chạy ví dụ của bạn. Tôi đoán nó chỉ hoạt động nếu DEP bị tắt.
Rasmus Faber

1
Nhưng nếu tôi cấp phát bộ nhớ bằng cờ EXECUTE_READWRITE và sử dụng nó trong trường _methodPtr thì nó hoạt động tốt. Nhìn qua mã Rotor, về cơ bản nó giống như những gì Marshal.GetDelegateForFunctionPointer () thực hiện, ngoại trừ việc nó bổ sung thêm một số phần mềm xung quanh mã để thiết lập ngăn xếp và xử lý bảo mật.
Rasmus Faber

Tôi nghĩ liên kết đã chết, than ôi, tôi sẽ chỉnh sửa nó, nhưng tôi không thể tìm thấy bản di chuyển của bản gốc.
Abel
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.