1 poin oleh GN⁺ 1 jam lalu | 1 komentar | Bagikan ke WhatsApp
  • Elevator menerjemahkan seluruh executable x86-64 ke AArch64 secara statis tanpa informasi debug, kode sumber, atau asumsi tata letak biner
  • Alih-alih memakai heuristik untuk membedakan kode dan data, ia membangun superset CFG yang memuat semua interpretasi yang mungkin untuk setiap byte lalu hanya menghapus jalur yang berakhir pada terminasi program
  • Elevator memetakan status x64 satu-banding-satu ke register AArch64 dan menangani percabangan tidak langsung dengan tabel lookup dari alamat asli ke kode hasil terjemahan
  • Bank tile offline menuliskan semantik instruksi x64 sebagai template C lalu mengompilasikannya menjadi urutan byte AArch64 dengan LLVM 20
  • Hasilnya adalah biner AArch64 mandiri tanpa translasi saat runtime, dan pada SPECint 2006 menunjukkan kinerja setara atau lebih baik daripada mode pengguna QEMU JIT

Tujuan Elevator

  • Elevator adalah penerjemah biner statis penuh yang memindahkan seluruh executable x86-64 ke AArch64
  • Tidak menggunakan informasi debug, kode sumber, pola kode biner asli, atau asumsi tentang tata letak biner
  • Penerjemah statis konvensional bergantung pada heuristik atau fallback runtime untuk membedakan kode dan data, tetapi Elevator menerjemahkan lebih dulu semua byte executable asli menurut setiap interpretasi yang mungkin
  • Karena byte apa pun bisa menjadi data, bagian dari opcode, atau bagian dari argumen opcode, Elevator membangun superset CFG yang mencakup semua aliran kontrol yang mungkin, lalu hanya membuang jalur yang mengarah ke penghentian program yang eksepsional
  • Keluarannya terdiri dari biner AArch64 mandiri yang memuat kode hasil terjemahan, biner x64 asli, tabel lookup alamat, dan driver runtime
  • Setelah translasi selesai, hasilnya dapat dijalankan tanpa JIT atau dukungan translasi runtime
  • Jika biner input yang sama diterjemahkan dua kali, hasil bit output akan sama, sehingga objek yang diuji, diverifikasi, disertifikasi, atau ditandatangani secara kriptografis identik dengan kode yang benar-benar didistribusikan
  • Biaya utamanya adalah pembengkakan ukuran kode, tetapi sebagai gantinya verifikasi sebelum distribusi menjadi lebih memungkinkan dibanding emulator atau kompiler JIT
  • Evaluasi mencakup seluruh benchmark SPECint 2006 dan biner buatan tangan, dengan performa setara atau lebih baik daripada emulasi mode pengguna QEMU yang dipercepat JIT
  • Tim peneliti menyatakan akan merilis seluruh proyek sebagai open source saat proyek selesai

Mengapa translasi statis dibutuhkan dan keterbatasan yang ada

  • Ketika perangkat keras berpindah dari satu ISA ke ISA lain, perangkat lunak lama perlu dibawa ke platform baru, dan sekadar mengompilasi ulang source code yang tersisa mungkin tidak cukup
  • Pada kode legacy yang telah diverifikasi atau disertifikasi, objek sertifikasi sering kali bukan source code, melainkan binary executable otoritatif tertentu yang sudah teruji dengan baik
  • Untuk mereproduksi biner yang sama persis per bit dari source code di kemudian hari, mungkin dibutuhkan versi compiler, linker, dan build system pada masa itu, yang secara praktis sulit dilakukan
  • Jika vendor pernah menerapkan patch langsung ke biner tanpa melalui source code, build ulang dari source yang diarsipkan bisa menghidupkan kembali bug yang sudah diperbaiki
  • Pendekatan yang langsung menangani biner yang ada biasanya menggabungkan emulasi, translasi statis, dan translasi dinamis, tetapi komponen sistem tambahan yang berjalan bersama program hasil terjemahan menjadi bagian dari trusted code base
  • Perilaku dinamis dapat berbeda tergantung urutan pengujian atau input, sehingga sulit memastikan keandalan keseluruhan
  • Horspool dan Marovac menunjukkan pada 1980 bahwa untuk membalik executable perlu membedakan kode dan data secara pasti, dan pada sebagian besar arsitektur hal ini ekuivalen dengan halting problem, sehingga secara umum tidak dapat diselesaikan
  • Pengangkat biner statis yang ada mendekati pembedaan kode dan data dengan heuristik, dan masalahnya makin besar terutama saat memprediksi target transfer aliran kontrol tidak langsung
  • LLBT mengangkat instruksi ARM ke LLVM IR lalu mengompilasi ulang ke arsitektur target, tetapi memakai heuristik untuk mendeteksi target percabangan tidak langsung dan membuat berbagai asumsi tentang biner input
  • Bahkan heuristik yang bagus pun gagal pada sebagian input, dan karena pengangkatan seluruh biner secara benar mengharuskan semua pembedaan kode-data akurat, peluang gagal meningkat seiring membesarnya biner
  • Pendekatan dinamis dapat mengikuti aliran instruksi yang benar-benar dieksekusi sehingga bisa menangani pemulihan instruksi dan aliran kontrol tidak langsung, tetapi tidak bisa mengangkat instruksi yang tidak tercapai dalam eksekusi konkret
  • Pada ISA seperti x64 yang memiliki instruksi panjang variabel, satu rangkaian instruksi bisa menumpang di dalam rangkaian instruksi lain, dan cabang ke tengah instruksi multibyte dapat membuat operand lama didekode sebagai instruksi terpisah
  • Serangan ROP dan obfuscation kode dapat memanfaatkan sifat ini
  • Rosetta II dari Apple dan Prism dari Microsoft menggabungkan komponen translasi awal dan translasi dinamis
  • WYTIWYG dan Polynima mengangkat secara statis jalur aliran kontrol yang diidentifikasi lewat profiling dinamis, lalu memakai fallback dinamis untuk mengumpulkan informasi aliran kontrol saat mencapai alamat target yang belum pernah terlihat
  • Elevator tidak memutuskan apakah sebuah byte adalah kode atau data, atau apakah ia adalah word instruksi atau argumen; sebaliknya, setiap byte executable dimasukkan ke jalur aliran kontrol terpisah menurut semua interpretasi yang mungkin
  • Pendekatan ini menerapkan superset disassembly pada rekompilasi statis dan kompilasi lintas ISA, dengan menukar presisi decoding dengan pertambahan ukuran kode

Pelestarian aliran kontrol dan status

  • Elevator bekerja berdasarkan prinsip mempertahankan seluruh status x64 di dalam kode AArch64 hasil terjemahan
  • Register x64 dan register AArch64 dipetakan satu-banding-satu, sehingga setiap status register x64 diemulasikan pada register AArch64 yang bersesuaian
  • Stack x64 diemulasikan langsung di atas stack AArch64, dan ekspansi stack normal saat eksekusi ditangani oleh sistem operasi
  • Elevator tidak menganalisis ABI biner x64 input; translasi ABI hanya dilakukan pada titik saat eksekusi keluar ke kode eksternal atau kembali darinya, mengikuti aturan x64 System V ABI dan AArch64 Procedure Call Standard
  • Berkat pelestarian status penuh dan korespondensi register satu-banding-satu, setiap instruksi x64 dapat diterjemahkan secara independen tanpa perlu mengetahui instruksi sebelum atau sesudahnya
  • Setiap byte offset yang dapat dieksekusi pada biner asli ditafsirkan sekaligus sebagai data dan sebagai titik awal potensial dari rangkaian instruksi
  • Untuk semua target potensial yang tidak dapat dianalisis secara statis, seperti indirect jump, callback, atau dispatch runtime, biner yang ditulis ulang akan memiliki titik pendaratan yang sesuai
  • Saat runtime, target diuraikan dengan tabel lookup yang tertanam di biner akhir untuk memetakan alamat instruksi asli ke alamat kode hasil terjemahan
  • Contoh instruksi bertumpuk

    • Listing 1 menunjukkan bahwa jika decoding dimulai dari .byte 0xB0, hasilnya MOV AL, 0xC3 diikuti RET, sedangkan jika dimulai satu byte kemudian di ReturnC2, hasilnya hanya RET
    • Kedua decode itu dapat dicapai dari jz sebelumnya, dan jika penerjemah hanya memilih satu interpretasi untuk dua byte tersebut, satu jalur akan terlewat
  • Contoh percabangan tidak langsung terhitung

    • Listing 2 menunjukkan call Label membuat alamat basis tabel, pop rsi mengambilnya kembali, lalu offset yang bergantung pada input ditambahkan untuk membentuk target jmp rsi
    • Cabang dapat mendarat pada salah satu dari empat instruksi inc eax yang ditempatkan dengan jarak 2 byte dalam stream encoding
    • Penerjemah yang hanya menulis ulang target lompatan yang bisa ditafsirkan secara statis tidak akan punya tempat untuk mendaratkan cabang seperti ini
  • Call, return, dan branch

    • Instruksi call, return, dan branch tidak dapat dinyatakan sebagai tile C karena lokasi alamat kembali, program counter, dan layout flag kondisi berbeda antara x64 dan AArch64
    • Direct call mendorong alamat kembali x64 asli ke stack emulasi lalu melakukan branch ke tile hasil terjemahan milik callee
    • Indirect call memeriksa apakah target berada di dalam biner hasil terjemahan atau di library eksternal; target internal diterjemahkan melalui tabel offset-x64-ke-tile lalu dilakukan branch ke tile terkait
    • Untuk target eksternal, alamat gadget translasi ABI balik ditempatkan di X30, translasi ABI keluar dilakukan, lalu dieksekusi branch ke target eksternal
    • Return mengambil alamat kembali 8 byte dari stack emulasi, membandingkannya dengan rentang biner x64 tertanam, dan jika itu return internal, alamat tersebut diterjemahkan lewat tabel lookup lalu dilakukan branch ke tile terkait
    • Direct branch memiliki target yang diketahui saat translasi, sedangkan branch kondisional diterjemahkan menjadi branch kondisional AArch64 yang memeriksa bit flag x64 yang disimpan di X14
    • Indirect branch mengeluarkan bounds check seperti pada indirect call dan return, dan jika targetnya eksternal maka translasi ABI keluar dijalankan

Pipeline translasi berbasis tile

  • Translasi Elevator dibagi menjadi tiga tahap: pembuatan bank tile offline, penulisan ulang per biner input, dan packaging akhir
  • Tahap offline menyatakan semantik instruksi x64 sebagai fungsi C, menspesialisasikannya untuk setiap kombinasi operand di bawah pemetaan register x64-ke-AArch64 yang tetap, lalu mengompilasinya dengan LLVM 20 yang dimodifikasi untuk menghasilkan urutan byte AArch64 yang dapat dipakai ulang
  • Tahap per biner input menjalankan superset disassembly, lalu untuk tiap instruksi kandidat yang ditemukan, mencari tile berdasarkan nama dan menyambungkan urutan byte AArch64
  • Kategori instruksi yang sulit dinyatakan sebagai tile C, seperti transfer aliran kontrol dan batas ABI, ditangani dengan template kecil buatan tangan
  • Tahap packaging menggabungkan kode hasil terjemahan, biner x64 asli, tabel lookup alamat, dan driver runtime untuk menghasilkan biner AArch64 yang dapat dieksekusi secara mandiri
  • Bank tile offline

    • Menulis secara manual rangkaian instruksi AArch64 yang ekuivalen untuk tiap instruksi x64 tidak praktis
    • Satu template seperti ADD Reg8, Reg8 saja berkembang menjadi 256 kombinasi register konkret, dan keseluruhan instruction set x64 memiliki banyak variasi pengalamatan untuk register, operand memori, dan immediate
    • Elevator menulis semantik tiap instruksi x64 sebagai fungsi C kecil, lalu menspesialisasikannya menurut kombinasi operand konkret dan membiarkan LLVM mengompilasikannya ke AArch64
    • Pada contoh ADD Reg8, Reg8, template memperbarui 8 bit bawah register tujuan dengan hasil penjumlahan 8 bit sambil mempertahankan 56 bit atas agar sesuai dengan semantik penulisan partial register x64
    • x64 ADD Reg8, Reg8 juga mengubah flag RFLAGS seperti Carry, Parity, Auxiliary Carry, Zero, Sign, dan Overflow, sehingga karena keterbatasan fungsi C yang hanya punya satu nilai kembali, pembaruan flag ditangkap sebagai tile flag terpisah
    • Satu instruksi x64 dapat berkorespondensi dengan satu atau beberapa tile, dan saat emisi tile-tile itu kembali disambung berurutan untuk memulihkan semantik penuhnya
    • Atribut aarch64_custom_reg menyatakan register AArch64 mana yang harus dipakai LLVM untuk menempatkan nilai kembali dan tiap argumen
    • Pemetaan tetap dipilih agar sifat callee-saved dan caller-saved antara x64 System V dan AAPCS64 selaras, mengurangi penataan ulang posisi register argumen integer, dan menyisakan register callee-saved AArch64 yang tidak terpakai untuk status bayangan di masa depan
    • Bit RFLAGS x64 dan berkas register XMM juga disimpan pada register AArch64 khusus dengan prinsip satu-banding-satu yang sama
    • LLVM 20 yang dimodifikasi menangani atribut aarch64_custom_reg per fungsi dan mengklasifikasikan ulang register AArch64 yang menyimpan status x64 emulasi sebagai callee-saved di dalam allocator register
    • TileGen menelusuri template C untuk membuat salinan terspesialisasi bagi setiap kombinasi operand yang diizinkan, lalu menyintesis atribut secara mekanis dari posisi parameter template dan pemetaan register
  • Penulisan ulang per biner input

    • Saat diberi biner x64 input, tahap per-binary menjalankan superset disassembly dan menelusuri CFG hasilnya
    • Pada tiap node, formatter membangun nama tile dari opcode dan operand instruksi yang telah didekode; untuk instruksi yang membutuhkan beberapa tile, beberapa nama digabungkan
    • x64 tidak memiliki pembatasan alignment stack pointer, tetapi AArch64 mengharuskan alignment 16 byte saat stack pointer digunakan sebagai operand memori
    • Jika RSP dipetakan langsung ke SP, pola kode x64 umum seperti PUSH beruntun di function prologue dapat memicu exception alignment di AArch64
    • Elevator membuat tile mengakses stack melalui register terpisah X25, dan hanya mengonkretkan SP di dalamnya saat tile benar-benar membutuhkannya
    • Tile yang dikompilasi dengan LLVM mengasumsikan alignment SP 16 byte saat masuk, sehingga sebelum menjalankan tile yang terdeteksi mengalokasikan spill space, SP disejajarkan ke bawah dan dipulihkan setelah eksekusi
    • Karena tile perhitungan flag relatif mahal, Elevator menghapus perhitungan flag pada node saat ini jika flag itu akan ditimpa sebelum dibaca oleh instruksi post-dominating berikutnya
    • Instruksi yang saat ini belum didukung terutama adalah AVX2 x64 dan ekstensi vektor lebar yang lebih baru, dan pada lokasi tersebut disisipkan instruksi interrupt sebagai pengganti tile
    • Dalam evaluasi penuh SPECint 2006, keseluruhan ISA integer x86-64 dan subset SSE yang dipakai SPECint sudah cukup untuk menjalankan semua benchmark
    • Dukungan instruksi tambahan dapat diperluas dengan menambahkan tile baru, tetapi para peneliti menilai rekayasa tambahan semacam itu kecil kemungkinan memberi wawasan ilmiah baru

Penanganan batas ABI

  • Elevator hanya mendukung biner dynamic linking
  • Biner static linking dapat langsung memuat instruksi spesifik arsitektur seperti CPUID, tetapi biner dynamic linking mendelegasikan hal itu ke libc, sehingga kebutuhan translasi berkurang
  • Saat berinteraksi dengan library dynamic linking, Elevator mendukung perpindahan antara ABI Linux x64 dan ABI Linux AArch64 untuk berpindah antara lingkungan x64 emulasi dan kode library AArch64 native
  • Elemen utama yang memerlukan translasi ABI adalah penempatan argumen dan lokasi alamat kembali
  • ABI System V x64 memakai enam register RDI, RSI, RDX, RCX, R8, R9 sebagai register argumen, dan argumen tambahan dikirim lewat stack mulai dari [RSP+8]
  • CALL x64 menyimpan alamat kembali di [RSP]
  • AArch64 Procedure Call Standard memakai delapan register argumen X0-X7, menaruh argumen sisanya di stack pada [SP], dan menyimpan alamat kembali di X30
  • Memanggil library eksternal

    • Jika call x64 hasil terjemahan menargetkan library eksternal, layout argumen harus diubah agar sesuai dengan calling convention AArch64
    • Pertama, SP dikurangi 8 agar kembali sejajar pada batas 16 byte, dan alamat kembali x64 yang sudah ada di stack ditempatkan di [SP+0x8]
    • Nilai pada [SP+0x10] dan [SP+0x18] dimuat ke X6 dan X7 agar argumen ke-7 dan ke-8 potensial yang diletakkan kode x64 di stack dapat dilihat oleh library AArch64
    • Argumen stack potensial yang tersisa tetap berada mulai dari [SP+0x20], sehingga tidak cocok dengan posisi yang diharapkan AArch64
    • Menghapus alamat kembali x64 dan nilai yang dipindahkan ke X6 dan X7 dari stack tidak aman karena nilai-nilai itu mungkin bukan argumen nyata, melainkan caller spill space atau sebagian struktur yang dialokasikan di stack caller
    • Elevator tidak mengutak-atik layout stack caller, melainkan mengalokasikan ruang stack tambahan sebesar n×8 byte lalu menyalin n argumen potensial 8 byte dari posisi saat ini
    • Nilai baku n adalah 10, dan jika biner input mengirim lebih dari total 16 argumen ke fungsi library eksternal, nilainya bisa dinaikkan lewat konfigurasi
    • Terakhir, alamat gadget tempat library eksternal akan kembali disimpan di X30
  • Kembali dari library eksternal

    • Ketika kontrol kembali ke gadget yang sebelumnya disimpan di X30 sebelum pemanggilan library eksternal, stack pointer ditambah n×8 untuk membersihkan argumen stack yang tadi disalin
    • Nilai kembali library eksternal dipindahkan dari X0 ke X9, yakni lokasi RAX yang diharapkan kode x64 emulasi
    • Alamat kembali x64 asli beserta padding terkait diambil dari stack, alamat itu diterjemahkan, lalu dilakukan branch ke sana untuk melanjutkan eksekusi setelah CALL asli
  • Callback yang masuk ke kode hasil terjemahan

    • Jika kode AArch64 native memanggil biner hasil terjemahan, calling convention AArch64 harus diubah ke calling convention x64
    • Kode x64 emulasi mengharapkan argumen ke-7 dan ke-8 berada di stack, bukan di X6 dan X7, sehingga X7 didorong lebih dulu lalu X6 agar ditempatkan pada posisi stack yang diharapkan x64
    • Jika callee sebenarnya tidak mengharapkan argumen ke-7 dan ke-8, nilai yang didorong ini tidak akan berdampak
    • Alamat kembali yang dimasukkan instruksi AArch64 branch-and-link ke X30 oleh library eksternal didorong ke lokasi stack yang diharapkan instruksi return x64
  • Kembali dari callback ke library eksternal

    • Saat kode hasil terjemahan kembali dari callback ke library eksternal, proses masuk tadi dibalik
    • Alamat kembali diambil dari stack, X6 dan X7 didorong, dan ruang stack yang dialokasikan dibersihkan dengan menambahkan 0x10 ke stack pointer

1 komentar

 
GN⁺ 1 jam lalu
Komentar Hacker News
  • Saya tidak tahu persis apa yang dilakukan user-mode JIT milik QEMU, tetapi tampaknya masih ada banyak ruang untuk perbaikan
    Pada 2013 saya membuat mesin JIT yang menerjemahkan dari x86-64 ke aarch64, dan saat itu saya bisa menjalankan biner Fedora beta aarch64 serta membangun ulang sebagian besar port Fedora untuk aarch64 di Linux x86_64
    Saya juga membuat JIT untuk arah sebaliknya, aarch64 → x86-64, dan untuk iseng saya bahkan sempat menunjukkan kedua JIT itu saling menjalankan dalam bentuk loopback di proses yang sama, seperti x86-64 → aarch64 → x86_64
    JIT yang saya buat memetakan instruksi dan status CPU dalam rasio satu-ke-banyak, dan kinerjanya kira-kira 2~5 kali lebih lambat dibanding kode yang dikompilasi ulang secara native
    Belakangan saat saya membandingkannya dengan QEMU JIT, QEMU tampak berada di kisaran 10~50 kali lebih lambat
    Sayangnya lisensinya tidak disiapkan sebagai open source, jadi saya tidak bisa merilis kode untuk membuktikannya

    • Benar, QEMU JIT itu target yang relatif mudah dikalahkan
      Terutama jika desainnya bisa dikhususkan hanya untuk “x86 ke aarch64” dan “user mode saja”, ada banyak keuntungan performa yang bisa didapat
      Dukungan user mode di QEMU lebih mirip lampiran “kebetulan juga berfungsi” dari dukungan emulasi sistem, dan keseluruhan struktur JIT-nya juga memakai pendekatan “guest → representasi menengah → host”, sehingga bagus untuk mendukung banyak arsitektur guest dan banyak arsitektur host, tetapi sulit memanfaatkan sifat khusus dari kombinasi guest/host tertentu seperti “x86 punya sedikit register integer jadi bisa dialokasikan secara hard” atau “kalau CPU aarch64 dibiarkan pada mode yang tepat, semantik floating-point yang rumit akan selalu cocok”
      Selain itu, dalam pengembangan QEMU, lebih banyak waktu dihabiskan untuk “mengemulasikan fitur arsitektur baru X” daripada mencari peluang optimasi performa, karena pihak yang membiayai pengembangan memang lebih mementingkan hal itu
    • QEMU lebih merupakan TCG daripada translator, dan karena dirancang agar berjalan pada n arsitektur, ada keterbatasan yang melekat
  • Membesarnya section .text hingga 50 kali memang sangat besar, tetapi sebagai harga untuk mendapatkan translasi yang sepenuhnya deterministik, tampaknya masih bisa diterima
    Dalam banyak kasus, perbedaan performa dibanding emulasi akan lebih besar daripada ketidaknyamanan akibat ukuran yang membengkak
    Menarik juga bahwa multithreading dan exception handling bukan tidak mungkin, melainkan memang berada di luar cakupan proyek ini
    Saya penasaran apakah langkah berikutnya adalah memangkas ruang kemungkinan dengan heuristik untuk mengurangi ukuran biner
    Itu memang akan merusak jaminan translasi, tetapi bisa membuat portabilitas biner lebih realistis dalam praktik

    • Bukan berarti selisih performanya terhadap emulasi akan lebih besar
      Translator ini jauh lebih lambat daripada Box64 atau FEX, dan kecuali Anda berada dalam situasi di mana JIT tidak bisa dipakai karena alasan apa pun, ini tetap pilihan yang lebih buruk
  • Saya selalu penasaran bagaimana translator menangani indirect jump
    Saat menganalisis biner, Anda hanya bisa menemukan potongan kode yang terhubung oleh direct jump dengan alamat tujuan yang diketahui
    Jadi apakah itu berarti setiap kali indirect jump terjadi, target fungsinya harus dicari, lalu diterjemahkan bila perlu, kemudian kembali ke kode hasil terjemahan? Bukankah itu lambat?
    Saya penasaran apakah ada cara yang lebih cepat, apakah alamat fungsi hasil terjemahan bisa dicocokkan dengan alamat fungsi asli, atau apakah dimasukkan jump pada alamat asli yang menuju kode hasil terjemahan

    • Translator yang saya buat cuma proyek hobi, tetapi saya memakai tabel besar yang menyatakan “kalau indirect jmp ke alamat X, blok yang sesuai ada di lokasi Y”
      Cara ini memang lebih lambat daripada direct jmp yang tidak memakai tabel, tetapi indirect jump di program asli memang sejak awal lebih lambat, dan biasanya tidak sering muncul di dalam loop yang penting bagi performa
  • Saya sangat suka ide superset control flow graph ini, tetapi bagi orang yang hendak membaca artikelnya, hal-hal berikut layak diketahui
    Waktu jalan menjadi sekitar 4,75 kali lebih cepat (lebih cepat dari QEMU tetapi masih cukup jauh lebih lambat dari Box64), jumlah instruksi yang dieksekusi meningkat 7 kali, dan ukuran biner meningkat 50 kali
    x86 ABI diemulasikan sampai sebelum pemanggilan eksternal
    Sebagian besar status CPU x86 seperti EFLAGS harus diemulasikan, dan mov yang kompleks juga harus dihitung satu per satu
    Hanya mendukung biner single-thread
    Tidak ada exception handling maupun stack unwinding
    Tidak mendukung seluruh instruction set

  • Pekerjaan yang menarik
    Saya belum melihatnya secara rinci, tetapi relative offset sepertinya masih bisa menjadi masalah
    Karena ukuran hasil pembangkitan kode pasti akan berbeda, rasanya tetap perlu semacam lapisan translasi atau MMU, dan ini kemungkinan besar terutama akan memengaruhi jump table dan branch internal
    Saya kebanyakan menangani barang-barang era 90-an, dan disassembler membuat banyak asumsi tentang awal dan akhir kode
    Namun kadang ada kasus di mana tanpa pengetahuan awal seperti pointer entry point di lokasi tetap, Anda bahkan tidak bisa menemukan bongkahan biner itu
    Setelah beberapa kali pass, sepertinya biner bisa dimurnikan menjadi “wilayah yang pasti kode”

  • Jika “Elevator mempertimbangkan semua interpretasi yang mungkin untuk setiap byte, menghasilkan terjemahan terpisah untuk masing-masing yang mungkin sebelumnya, dan [...] hanya memangkas yang berujung crash”, apakah berarti semua program nyata yang punya kemungkinan collision akan terpangkas semuanya?

    • Mungkin itu akan diatur ke jalur collision yang distandardisasi di tabel lookup alamat→kode
      Jadi tetap akan terjadi collision, tetapi tidak akan sama dengan crash akibat kode salah yang dieksekusi secara langsung
  • Bagi saya, bagian yang paling menarik adalah dari sudut pandang sertifikasi
    Di industri yang teregulasi seperti penerbangan atau perangkat medis, kode yang dijalankan harus berupa kode yang tersertifikasi, sehingga sering kali JIT tidak bisa dipakai justru karena alasan seperti ini
    Translasi statis yang menghasilkan biner yang bisa ditandatangani dapat menjadi terobosan nyata meskipun harus menerima pembengkakan kode

    • Saya penasaran seberapa besar bidang ini dalam industri perangkat lunak
      Mungkin di area ini juga tidak ada cara untuk menerapkan LLM dalam skala besar, tetapi dalam wacana besar tentang “AI di tempat kerja”, bagian seperti ini hampir tidak pernah dibahas
  • 50 kali itu tidak masuk akal, benar-benar bencana cache
    Keuntungan performa yang didapat dengan menghindari JIT bisa habis termakan semuanya

    • Itu hanya benar jika semua kode tersebut benar-benar dipakai saat runtime, sedangkan sebagian besar titik awal decoding yang mungkin kemungkinan tidak akan digunakan
    • Ini adalah kasus yang sangat cocok untuk code relocation saat link time
      Jika kode panas dikumpulkan di satu tempat, kode yang tidak dipakai bisa dibuat tidak pernah dimuat sama sekali
    • Saya tidak akan buru-buru menarik kesimpulan
      Instruksi toh tidak sebesar itu, dan CPU juga melakukan optimasi saat berjalan
  • Apakah ini bisa menangani self-modifying code?
    Saya juga penasaran kenapa hanya x86_64
    Terasa lebih bermakna kalau yang diterjemahkan justru program 32-bit seperti game lama

    • Kalau membaca artikel yang ditautkan, bagian ini dibahas secara eksplisit
      “Self-modifying dan kode hasil kompilasi JIT. Elevator, seperti semua penulis ulang biner yang sepenuhnya statis, tidak mendukung self-modifying code maupun kode hasil kompilasi JIT”
    • Saya rasa self-modifying code di luar runtime JIT sekarang cukup jarang dibanding era 80~90-an
      Section .text modern umumnya read-only, dan tuntutan keamanan tidak akan berkurang
    • Jika menangani self-modifying code, maka itu tidak lagi “sepenuhnya statis”
      Secara mendasar itu kontradiktif
    • Dari sudut pandang orang yang mengembangkan x86 baru, self-modifying code meskipun memungkinkan, biasanya tetap mengerikan
      Karena itu merusak performa cache line dan prediksi cabang pipeline
      Selain itu, hal itu melanggar W^X sehingga biasanya hanya boleh dilakukan pada halaman memori yang kompatibel dengan JIT
      Karena itu hampir selalu harus dihindari
      Pada era 486 atau P5, ini sempat dipakai sampai batas tertentu, misalnya dengan memakai immediate value sebagai variabel loop internal, tetapi sekarang sudah tidak terlalu begitu
      Untuk mencapai emulasi atau translasi x86 yang nyaris sempurna, ada banyak kasus pengecualian kotor yang harus ditangani
  • Di mana source code-nya?