Biên dịch lại tĩnh là một cách hứa hẹn dịch các tệp nhị phân từ kiến trúc nước ngoài sang kiến trúc đích khác. Nó sẽ nhanh hơn Just-In-Time (JIT), bởi vì nó không phải biên dịch mã ngay trước khi chạy, và bởi vì thời gian biên dịch bổ sung nó có thể mất là hữu ích để tối ưu hóa mã tạo.
Tuy nhiên biên dịch JIT sử dụng phân tích chương trình động, trong khi biên dịch lại tĩnh dựa trên phân tích chương trình tĩnh (do đó tên).
Trong phân tích tĩnh, bạn không có thông tin thời gian chạy trên thực thi.
Một vấn đề lớn với điều này là do nhảy gián tiếp. Thuật ngữ này bao gồm mã có thể được tạo ra từ một số câu lệnh switch
nhất định, từ việc sử dụng các con trỏ hàm hoặc từ đa hình thời gian chạy (nghĩ rằng bảng ảo). Tất cả nắm xuống một lệnh có dạng:
JMP reg_A
Hãy nói rằng bạn biết địa chỉ bắt đầu của chương trình của bạn, và bạn quyết định bắt đầu biên dịch lại hướng dẫn từ thời điểm này. Khi bạn gặp phải một bước nhảy trực tiếp, bạn đi đến địa chỉ đích của nó, và bạn tiếp tục biên dịch lại từ đó. Khi bạn gặp phải một bước nhảy gián tiếp, bạn bị kẹt. Trong hướng dẫn lắp ráp này, nội dung của reg_A
không được biết đến tĩnh. Vì vậy, chúng tôi không biết địa chỉ của lệnh kế tiếp. Lưu ý rằng trong biên dịch động, chúng tôi không có vấn đề này, bởi vì chúng tôi mô phỏng trạng thái ảo của thanh ghi và chúng tôi biết nội dung hiện tại của reg_A
. Bên cạnh đó, trong biên dịch tĩnh, bạn quan tâm đến việc tìm kiếm tất cả giá trị có thể cho reg_A
tại thời điểm này, bởi vì bạn muốn có tất cả các đường dẫn có thể được biên dịch. Trong phân tích động, bạn chỉ cần giá trị hiện tại để tạo đường dẫn mà bạn hiện đang thực thi, nên reg_A
thay đổi giá trị của nó, bạn vẫn có thể tạo các đường dẫn khác. Trong một số trường hợp, phân tích tĩnh có thể tìm thấy một danh sách các ứng viên (nếu nó là switch
thì phải có một bảng có thể bù đắp ở đâu đó), nhưng trong trường hợp chung chúng ta chỉ đơn giản là không biết.
Phạt tiền, bạn nói, hãy biên dịch lại tất cả các hướng dẫn trong tệp nhị phân!
Vấn đề ở đây là trong hầu hết các tệp nhị phân chứa cả mã và dữ liệu. Tùy thuộc vào kiến trúc, bạn có thể không biết được cái nào.
Tệ hơn nữa, trong một số kiến trúc không có ràng buộc căn chỉnh và hướng dẫn chiều rộng biến, và bạn có thể bắt đầu tháo rời tại một số điểm, chỉ để khám phá ra rằng bạn đã bắt đầu biên dịch lại với bù đắp.
Chúng ta hãy tập lệnh đơn giản hóa bao gồm hai hướng dẫn và đăng ký đơn A
:
41 xx (size 2): Add xx to `A`.
42 (size 1): Increment `A` by one.
Hãy lấy chương trình nhị phân sau:
41 42
Hãy nói rằng điểm bắt đầu là byte đầu tiên 41
. Bạn làm:
41 42 (size 2): Add 42 to `A`.
Nhưng nếu 41 là một phần dữ liệu thì sao? Sau đó, chương trình của bạn trở thành:
42 (size 1): Increment `A` by one.
Vấn đề này được phóng đại trong các trò chơi cũ, mà thường được tối ưu hóa trực tiếp trong lắp ráp, và nơi mà các lập trình viên sức mạnh cố ý expect some byte to be interpreted as both code and data, depending on the context!
Thậm chí tệ hơn, chương trình biên dịch có thể tạo ra mã chính nó! Hãy tưởng tượng biên dịch lại trình biên dịch JIT. Kết quả sẽ vẫn là mã đầu ra cho kiến trúc nguồn và cố gắng nhảy vào nó, rất có thể khiến chương trình này chết rất sớm. Mã biên dịch lại tĩnh chỉ có sẵn trong thời gian chạy yêu cầu thủ thuật vô hạn!
Phân tích nhị phân tĩnh là một lĩnh vực nghiên cứu rất trực tiếp (chủ yếu trong lĩnh vực an ninh, để tìm lỗ hổng trong các hệ thống không có sẵn nguồn), và thực sự tôi biết nỗ lực sản xuất NES emulator that tries to statically recompile programs. Bài viết rất thú vị.
Sự thỏa hiệp giữa JIT và biên dịch lại tĩnh sẽ được biên dịch tĩnh càng nhiều mã càng tốt, chỉ giữ các bit không thể dịch được tĩnh.
Nó được thực hiện một vài lần. Ví dụ, FX's DEC! 32 biên dịch lại các x86 nhị phân để chạy trên DEC Alpha, thường nhanh hơn bất kỳ x86 nào trong thời gian có thể. Nó không đủ để bù đắp cho việc quản lý của DEC (mis), và Compaq/HP không quan tâm nhiều đến nó. –
Tại sao bộ mô phỏng lại tồn tại nếu như vậy? Đây có phải là REALLY * đó * khó hơn nhiều so với viết một trình giả lập không? – user1483857