1 puan yazan GN⁺ 1 시간 전 | 1 yorum | WhatsApp'ta paylaş
  • Elevator, debug bilgisi, kaynak kod veya ikili yerleşimi varsayımları olmadan x86-64 çalıştırılabilir dosyalarının tamamını statik olarak AArch64'e çevirir
  • Kod/veri ayrımı için sezgisel yöntemler yerine, her baytın tüm olası yorumlarını içeren bir superset CFG oluşturur ve yalnızca sonlanma yollarını kaldırır
  • x64 durumunu AArch64 yazmaçlarına bire bir eşler ve dolaylı dallanmaları, özgün adresten çevrilmiş koda giden bir arama tablosu ile işler
  • Çevrimdışı tile bank, x64 komut anlambilimini C şablonları olarak yazar, ardından bunları LLVM 20 ile AArch64 bayt dizilerine derler
  • Ortaya çıkan çıktı, çalışma anı çevirisi olmayan, kendi kendini içeren bir AArch64 ikilisidir ve SPECint 2006 üzerinde QEMU kullanıcı modu JIT ile aynı ya da daha iyi performans verir

Elevator'ın hedefi

  • Elevator, x86-64 çalıştırılabilir dosyalarının tamamını AArch64'e taşıyan tam statik bir ikili çeviricidir
  • Debug bilgisi, kaynak kod, özgün ikilinin kod kalıpları veya ikili yerleşimi hakkında varsayımlar kullanmaz
  • Mevcut statik çeviriciler kod ile veriyi ayırmak için sezgisel yöntemlere veya çalışma anı geri dönüşlerine dayanırken, Elevator özgün çalıştırılabilir dosyanın tüm baytlarını olası her yorum için önceden çevirir
  • Herhangi bir bayt veri, opcode'un bir parçası veya opcode bağımsız değişkeninin bir parçası olabileceğinden, mümkün tüm kontrol akışlarını içeren bir superset CFG oluşturur ve yalnızca istisnai program sonlandırmasına giden yolları kaldırır
  • Çıktı; çevrilmiş kod, özgün x64 ikilisi, adres arama tablosu ve çalışma anı sürücüsünü içeren kendi kendini içeren bir AArch64 ikilisinden oluşur
  • Çeviri tamamlandıktan sonra JIT veya çalışma anı çeviri desteği olmadan çalıştırılabilir
  • Aynı girdi ikilisi iki kez çevrilirse aynı çıktı bit dizisi üretilir; test, doğrulama, sertifikasyon ve kriptografik imzalama için hedeflenen şey gerçek dağıtım koduyla aynıdır
  • Başlıca maliyet kod boyutunun büyümesidir; bunun karşılığında emülatör veya JIT derleyicilere kıyasla dağıtımdan önce doğrulanabilirlik artar
  • Değerlendirmede tam SPECint 2006 kıyaslaması ve elle hazırlanmış ikililer yer aldı; performans, JIT hızlandırmalı QEMU kullanıcı modu emülasyonu ile aynı ya da daha iyi düzeyde çıktı
  • Araştırmacılar, proje tamamlandığında her şeyi açık kaynak olarak yayımlayacaklarını belirtiyor

Neden statik çeviri gerekli ve mevcut sınırlamalar neler

  • Donanım bir ISA'dan başka bir ISA'ya geçtiğinde mevcut yazılımın yeni platforma taşınması gerekir ve elde kalan kaynak kodu yeniden derlemek tek başına yeterli olmayabilir
  • Doğrulanmış veya sertifikalı eski kodlarda, kaynak kod değil, iyi test edilmiş belirli bir yetkili ikili çalıştırılabilir dosya çoğu zaman sertifikasyonun konusudur
  • Daha sonra kaynaktan aynı ikiliyi bit düzeyinde yeniden üretmek için dönemin derleyici, bağlayıcı ve build sistemi sürümlerine ihtiyaç duyulabilir; bu da pratikte zordur
  • Üretici kaynak koddan geçmeden doğrudan ikiliye yama uyguladıysa, arşivlenmiş kaynaktan yeniden build almak daha önce düzeltilmiş hataları geri getirebilir
  • Mevcut doğrudan ikili işleme yaklaşımları emülasyon, statik çeviri ve dinamik çeviriyi birleştirir; ancak çevrilmiş programla birlikte çalışan ek sistem bileşenleri güven tabanına dahil olur
  • Dinamik davranış, test sırasına veya girdilere göre değişebileceğinden, tüm güvenilirliği doğrulamak zordur
  • Horspool ve Marovac, 1980'de çalıştırılabilir dosyaları tersine çevirmek için kod ile verinin kesin biçimde ayrılması gerektiğini ve çoğu mimaride bunun durdurma problemi ile eşdeğer olduğu için genel durumda çözülemez olduğunu gösterdi
  • Mevcut statik ikili lifter'lar kod/veri ayrımını sezgisel olarak yaklaşıklar; özellikle dolaylı kontrol akışı aktarımı hedeflerini öngörmede sorun büyür
  • LLBT, ARM komutlarını LLVM IR'a kaldırıp hedef mimari için yeniden derler; ancak dolaylı dal hedeflerini saptamak için sezgisel yöntemler kullanır ve girdi ikilisi hakkında çeşitli varsayımlarda bulunur
  • İyi sezgisel yöntemler bile bazı girdilerde başarısız olur; tüm ikiliyi doğru kaldırmak için tüm kod/veri ayrımlarının doğru olması gerektiğinden, ikili büyüdükçe başarısızlık olasılığı artar
  • Dinamik yaklaşımlar gerçekten yürütülen komut akışını izlediği için komut kurtarma ve dolaylı kontrol akışını işleyebilir; ancak somut yürütmede ulaşılamayan komutları kaldıramaz
  • x64 gibi değişken uzunluklu komutlara sahip ISA'larda, bir komut dizisinin içine başka komut dizileri gömülebilir ve çok baytlı bir komutun ortasına dallanıldığında mevcut işlenenler ayrı komutlar olarak decode edilebilir
  • ROP saldırıları ve kod obfuscation bu özelliği kullanabilir
  • Apple'ın Rosetta II'si ve Microsoft'un Prism'i ön çeviri ile dinamik çeviri bileşenlerini birleştirir
  • WYTIWYG ve Polynima, dinamik profillemeyle belirlenen kontrol akışı yolları boyunca statik lifting yapar ve görülmemiş hedef adreslere ulaşıldığında kontrol akışı bilgisini dinamik olarak toplayan bir fallback kullanır
  • Elevator, herhangi bir baytın kod mu veri mi, komut sözcüğü mü yoksa bağımsız değişken mi olduğuna karar vermez; çalıştırılabilir dosyanın her baytını mümkün tüm yorumlarıyla ayrı kontrol akışı yollarına dahil eder
  • Bu yaklaşım, superset disassembly yöntemini statik yeniden derleme ve çapraz ISA derlemeye uygular; decode hassasiyetini kod büyümesiyle takas eder

Kontrol akışı ve durumun korunması

  • Elevator, çevrilmiş AArch64 kodu içinde x64 durumunun tamamını koruma ilkesine göre çalışır
  • x64 yazmaçları ile AArch64 yazmaçlarını bire bir eşleyerek her x64 yazmaç durumunu karşılık gelen AArch64 yazmacında emüle eder
  • x64 yığını doğrudan AArch64 yığını üzerinde emüle edilir ve yürütme sırasında olağan yığın genişlemesini işletim sistemi yönetir
  • Girdi x64 ikilisinin ABI'sini analiz etmez; yalnızca yürütmenin dış koda geçtiği veya geri döndüğü noktalarda x64 System V ABI ile AArch64 Procedure Call Standard kurallarına göre ABI çevirisi yapar
  • Tam durum koruması ve bire bir yazmaç eşlemesi sayesinde her x64 komutu, öncesindeki veya sonrasındaki komutları bilmeden bağımsız olarak çevrilebilir
  • Özgün ikilideki her çalıştırılabilir bayt ofseti, aynı anda hem veri hem de potansiyel bir komut dizisinin başlangıç noktası olarak yorumlanır
  • Dolaylı atlama, callback veya çalışma anı dispatch gibi statik olarak analiz edilemeyen tüm potansiyel hedefler için yeniden yazılmış ikilinin içinde karşılık gelen bir iniş noktası oluşturulur
  • Çalışma anında, hedefleri çözümlemek için özgün komut adresinden çevrilmiş kod adresine giden bir arama tablosu son ikiliye gömülür
  • İç içe geçmiş komut örneği

    • Listing 1, decode işlemi .byte 0xB0 adresinden başlatıldığında MOV AL, 0xC3 ardından RET üretildiğini, bir bayt sonraki ReturnC2 adresinden başlatıldığında ise yalnızca RET üretildiğini gösterir
    • Her iki decode da önceki jz üzerinden erişilebilir; çevirici bu iki bayt için yalnızca tek bir yorum seçerse yollardan birini kaçırır
  • Hesaplanmış dolaylı dal örneği

    • Listing 2, call Label komutunun tablo taban adresini oluşturduğunu, pop rsi ile bunun geri alındığını, ardından girdiye bağlı ofset eklenerek jmp rsi hedefinin oluşturulduğunu gösterir
    • Dal, encoding akışı içinde 2 bayt aralıklarla yerleştirilmiş dört inc eax komutundan birine inebilir
    • Yalnızca statik olarak çözümlenebilen atlama hedeflerini yeniden yazan bir çeviricinin böyle bir dal için iniş yapacak yeri olmaz
  • Çağrı, dönüş ve dal

    • call, return ve branch komutları; dönüş adresi konumu, program sayacı ve koşul bayrağı yerleşimi x64 ile AArch64 arasında farklı olduğu için C tile'larıyla ifade edilemez
    • Doğrudan çağrılar, özgün x64 dönüş adresini emüle edilen yığına push eder ve callee'nin çevrilmiş tile'ına dallanır
    • Dolaylı çağrılar, hedefin çevrilmiş ikili içinde mi yoksa harici bir kütüphanede mi olduğunu denetler; iç hedefler x64 ofset-tile tablosu ile çevrilerek ilgili tile'a dallanır
    • Dış hedeflerde, AArch64 kütüphanesinin döneceği X30 içine ters ABI çeviri gadget adresi konur, çıkış ABI çevirisi yapılır ve ardından dış hedefe dallanılır
    • Dönüşte, emüle edilen yığından 8 baytlık dönüş adresi alınır, gömülü x64 ikili aralığıyla karşılaştırılır ve iç dönüşse adres arama tablosuyla çevrilerek ilgili tile'a dallanılır
    • Doğrudan dallarda hedef çeviri zamanında bilinir; koşullu dallar, X14 içinde tutulan x64 bayrak bitlerini inceleyen AArch64 koşullu dalına çevrilir
    • Dolaylı dallar, dolaylı çağrı ve dönüştekiyle aynı bounds check'i üretir; hedef dışarıdaysa çıkış ABI çevirisi yapılır

Tile tabanlı çeviri hattı

  • Elevator'ın çevirisi üç aşamaya ayrılır: çevrimdışı tile bank üretimi, girdi ikilisi başına yeniden yazım ve son paketleme
  • Çevrimdışı aşama, x64 komut anlambilimini C fonksiyonlarıyla ifade eder; bunu sabit x64-to-AArch64 yazmaç eşlemesi altında işlenen kombinasyonlarına göre özelleştirir ve değiştirilmiş LLVM 20 ile derleyerek yeniden kullanılabilir AArch64 bayt dizileri üretir
  • Girdi ikilisi başına aşama, superset disassembly gerçekleştirir ve bulunan her aday komut için ada göre tile arayıp AArch64 bayt dizilerini art arda ekler
  • Kontrol akışı aktarımları ve ABI sınırları gibi C tile'larıyla ifade edilmesi zor komut kategorileri, elle hazırlanmış küçük şablonlarla işlenir
  • Paketleme aşaması, çevrilmiş kodu, özgün x64 ikilisini, adres arama tablosunu ve çalışma anı sürücüsünü birleştirerek bağımsız çalıştırılabilir bir AArch64 ikilisi üretir
  • Çevrimdışı tile bank

    • Her x64 komutu için eşdeğer AArch64 komut dizilerini elle yazmak pratik değildir
    • ADD Reg8, Reg8 gibi tek bir şablon bile 256 somut yazmaç kombinasyonuna genişler; tüm x64 komut kümesinde yazmaç, bellek işleneni ve immediate adresleme çeşitleri çok fazladır
    • Elevator, her x64 komutunun anlambilimini küçük bir C fonksiyonu olarak yazar, ardından bunu somut işlenen kombinasyonlarına göre özelleştirir ve LLVM'in AArch64'e derlemesini sağlar
    • ADD Reg8, Reg8 örneğinde şablon, hedef yazmacın alt 8 bitini 8 bitlik toplamla günceller ve üst 56 biti koruyarak x64'ün kısmi yazmaç yazma anlambilimini karşılar
    • x64 ADD Reg8, Reg8, RFLAGS içindeki Carry, Parity, Auxiliary Carry, Zero, Sign ve Overflow bayraklarını da değiştirdiğinden, tek dönüş değerine sahip C fonksiyonu kısıtı nedeniyle bayrak güncellemesi ayrı bir bayrak tile'ı ile yakalanır
    • Bir x64 komutu bir veya birden çok tile'a karşılık gelebilir; üretim sırasında bunlar yeniden art arda yapıştırılarak tam anlambilim geri kurulur
    • aarch64_custom_reg özniteliği, LLVM'e dönüş değerini ve her bağımsız değişkeni hangi AArch64 yazmaçlarına yerleştireceğini bildirir
    • Sabit eşleme; x64 System V ile AAPCS64'ün callee-saved ve caller-saved özelliklerini uyumlu hale getirmek, tamsayı bağımsız değişken yazmaçlarındaki yeniden sıralamayı azaltmak ve boşta kalan AArch64 callee-saved yazmaçlarını gelecekte gölge durum için ayırmak amacıyla seçilmiştir
    • x64'ün RFLAGS bitleri ve XMM yazmaç dosyası da aynı bire bir ilke altında ayrılmış AArch64 yazmaçlarında tutulur
    • Değiştirilmiş LLVM 20, fonksiyon başına aarch64_custom_reg özniteliğini işler ve emüle edilen x64 durumunu tutan AArch64 yazmaçlarını register allocator içinde callee-saved olarak yeniden sınıflandırır
    • TileGen, C şablonlarını dolaşarak izin verilen her işlenen kombinasyonu için özelleştirilmiş kopyalar üretir ve şablonun parametre konumları ile yazmaç eşlemesinden öznitelikleri mekanik olarak sentezler
  • Girdi ikilisi başına yeniden yazım

    • Girdi x64 ikilisi verildiğinde ikili başına aşama superset disassembly yapar ve elde edilen CFG üzerinde dolaşır
    • Her düğümde formatter, decode edilen komutun opcode ve işlenenlerinden tile adını oluşturur; birden çok tile gerektiren komutlar için birden çok adı birleştirir
    • x64'te yığın işaretçisi hizalaması kısıtı yoktur, ancak AArch64 yığın işaretçisi bellek işleneninde kullanıldığında 16 bayt hizalama ister
    • RSP doğrudan SP'ye eşlenirse, işlev prologlarındaki art arda PUSH gibi yaygın x64 kod kalıpları AArch64'te hizalama istisnası oluşturabilir
    • Elevator, tile'ların yığına ayrı bir X25 yazmacı üzerinden erişmesini sağlar ve yalnızca gerçekten gerektiğinde SP'yi bunun içinde somutlaştırır
    • LLVM ile derlenmiş tile'lar girişte 16 baytlık SP hizalaması beklediğinden, spill alanı ayırdığı saptanan tile'lar yürütülmeden önce SP aşağı hizalanır ve yürütmeden sonra geri yüklenir
    • Bayrak hesaplama tile'ları görece pahalı olduğundan, bayraklar sonraki post-dominating komutlarda okunmadan önce üzerine yazılıyorsa mevcut düğümdeki bayrak hesaplaması kaldırılır
    • Şu anda desteklenmeyen komutlar çoğunlukla x64'ün AVX2 ve sonrası geniş vektör uzantılarıdır; bu konumlara tile yerine interrupt komutu eklenir
    • Tam SPECint 2006 değerlendirmesinde, tüm kıyaslamaları çalıştırmak için tam x86-64 tamsayı ISA'sı ile SPECint'in kullandığı SSE alt kümesi yeterli oldu
    • Ek komut desteği yeni tile'lar eklenerek genişletilebilir; ancak araştırmacılara göre daha fazla mühendislik çabası bilimsel içgörüye çok az katkı sağlayacaktır

ABI sınırlarının işlenmesi

  • Elevator yalnızca dinamik bağlantılı ikilileri destekler
  • Statik bağlantılı ikililer CPUID gibi mimariye özgü komutları doğrudan içerebilir; dinamik bağlantılı ikililer bunları libc'ye devrettiği için çeviri ihtiyacı azalır
  • Dinamik bağlantılı kütüphanelerle etkileşimde, emüle edilen x64 ortamı ile yerel AArch64 kütüphane kodu arasında gidip gelmek için x64 Linux ABI ile AArch64 Linux ABI arasındaki geçişleri destekler
  • ABI çevirisi gerektiren temel öğeler bağımsız değişken yerleşimi ve dönüş adresinin konumudur
  • System V x64 ABI, RDI, RSI, RDX, RCX, R8, R9 olmak üzere altı yazmacı bağımsız değişken yazmacı olarak kullanır ve ek bağımsız değişkenleri [RSP+8] konumundan başlayarak yığında geçirir
  • x64 CALL, dönüş adresini [RSP] konumuna yazar
  • AArch64 Procedure Call Standard, X0-X7 olmak üzere sekiz bağımsız değişken yazmacı kullanır, geri kalan bağımsız değişkenleri [SP] yığınında tutar ve dönüş adresini X30 içinde saklar
  • Harici kütüphane çağrıları

    • Çevrilmiş x64 çağrısı bir harici kütüphaneyi hedefliyorsa, bağımsız değişken yerleşimi AArch64 çağrı kuralına göre dönüştürülmelidir
    • Önce SP'den 8 çıkarılarak 16 bayt sınırına yeniden hizalanır ve daha önce yığında bulunan x64 dönüş adresi [SP+0x8] konumuna yerleştirilir
    • [SP+0x10] ve [SP+0x18] konumlarındaki değerler X6 ve X7 içine yüklenerek x64 kodunun yığına koyduğu olası 7. ve 8. bağımsız değişkenlerin AArch64 kütüphanesi tarafından görülebilmesi sağlanır
    • Geri kalan olası yığın bağımsız değişkenleri [SP+0x20] konumundan itibaren kalır; bu da AArch64'ün beklediği yerleşimle uyuşmaz
    • x64 dönüş adresini ve X6, X7 içine taşınan değerleri yığından kaldırmak güvenli değildir; çünkü bunlar gerçek bağımsız değişken değil, çağıranın spill alanı ya da yığına yerleştirilmiş bir yapının parçası olabilir
    • Elevator, çağıranın yığın yerleşimine dokunmadan ek n×8 bayt yığın alanı ayırır ve mevcut konumdan olası n adet 8 baytlık bağımsız değişkeni buraya kopyalar
    • Varsayılan n değeri 10'dur; girdi ikilisi harici kütüphane fonksiyonlarına toplam 16'dan fazla bağımsız değişken geçiriyorsa bu değer yapılandırmayla artırılabilir
    • Son olarak, harici kütüphanenin döneceği gadget adresi X30 içine kaydedilir
  • Harici kütüphaneden geri dönüş

    • Denetim, harici kütüphane çağrısından önce X30 içine kaydedilen gadget'a döndüğünde, daha önce kopyalanmış yığın bağımsız değişkenlerini temizlemek için yığın işaretçisine n×8 eklenir
    • Harici kütüphane dönüş değeri X0'dan, emüle edilen x64 kodunun RAX için beklediği konum olan X9'a taşınır
    • Özgün x64 dönüş adresi ve ilgili padding yığından alınır, adres çevrilir ve oraya dallanılarak özgün CALL sonrasındaki yürütmeye devam edilir
  • Çevrilmiş koda gelen callback'ler

    • Yerel AArch64 kodu çevrilmiş ikiliyi çağırdığında, AArch64 çağrı kuralı x64 çağrı kuralına dönüştürülmelidir
    • Emüle edilen x64 kodu 7. ve 8. bağımsız değişkenleri X6, X7 içinde değil yığında beklediğinden, önce X7, ardından X6 push edilerek x64'ün beklediği yığın konumlarına yerleştirilir
    • Callee gerçekte 7. ve 8. bağımsız değişkenleri beklemiyorsa bu push edilen değerlerin etkisi olmaz
    • Harici kütüphanenin AArch64 branch-and-link komutunun X30 içine koyduğu dönüş adresi, x64 dönüş komutunun beklediği yığın konumuna push edilir
  • Callback'ten harici kütüphaneye dönüş

    • Çevrilmiş kod callback içinden harici kütüphaneye dönerken giriş süreci tersine çevrilir
    • Dönüş adresi yığından alınır ve ayrılmış yığın alanı X6 ile X7 push edilip ardından yığın işaretçisine 0x10 eklenerek temizlenir

1 yorum

 
GN⁺ 1 시간 전
Hacker News yorumları
  • QEMU’nun kullanıcı modu JIT’inin tam olarak ne yaptığını bilmiyorum ama iyileştirme için epey alan var gibi görünüyor
    2013’te x86-64’ten aarch64’e çeviren bir JIT motoru yaptım ve o zaman Fedora’nın beta aarch64 ikililerini çalıştırarak x86_64 Linux üzerinde Fedora’nın aarch64 portunun büyük kısmını yeniden derleyebiliyordum
    Ters yönde, yani aarch64 → x86-64 JIT de yaptım; eğlencesine aynı süreç içinde x86-64 → aarch64 → x86_64 şeklinde iki JIT’in birbirini loopback tarzında çalıştırdığını da göstermiştim
    Yaptığım JIT, komutları ve CPU durumunu bire-çok eşliyordu ve yerel yeniden derlenmiş koda kıyasla yaklaşık 2 ila 5 kat daha yavaştı
    Daha sonra QEMU JIT ile karşılaştırdığımda, QEMU’nun 10 ila 50 kat daha yavaş aralığında göründüğünü fark ettim
    Ne yazık ki açık kaynak lisanslaması yapılmadığı için bunu kanıtlayacak kodu yayımlayamıyorum

    • Evet, QEMU JIT yenmesi nispeten kolay bir hedefe yakın
      Özellikle tasarımı “yalnızca x86’den aarch64’e” ve “yalnızca kullanıcı modu” için özelleştirebiliyorsanız ciddi performans kazanımı elde edilebilir
      QEMU’nun kullanıcı modu desteği, sistem emülasyonu desteğine eklenmiş “bir şekilde çalışan” bir ek gibi ve genel JIT yapısı da “misafir → ara gösterim → ana sistem” biçiminde; bu, birçok misafir mimariyi ve birçok ana sistem mimarisini desteklemek için iyi, ancak “x86’in tamsayı register’ı az olduğu için sabit atama yapılabilir” ya da “aarch64 CPU uygun moda alınırsa karmaşık kayan nokta anlambilimi her zaman doğru olur” gibi belirli misafir/ana sistem eşleşmelerinin özelliklerinden yararlanmayı zorlaştırıyor
      Ayrıca QEMU geliştirmesinde, performans optimizasyon fırsatları aramaktan çok “yeni mimari özelliği X’i emüle etmek” için zaman harcanıyor; çünkü geliştirme maliyetini üstlenenler buna daha fazla önem veriyor
    • QEMU bir çeviriciden çok TCG ve n mimaride çalışacak şekilde tasarlandığı için sınırlamaları var
  • .text bölümünün 50 kat büyümesi çok büyük, ama tamamen deterministik bir çeviri elde etmenin bedeli olarak yine de makul görünüyor
    Çoğu durumda boyut artışının yarattığı rahatsızlıktan çok, emülasyona kıyasla performans farkı daha önemli olacaktır
    Çok iş parçacıklılığı ve istisna işlemenin imkânsız değil de bu projenin kapsamı dışında olması da ilginç
    Bir sonraki adımın, olasılık uzayını sezgisel yöntemlerle budayıp ikili boyutunu küçültmek olup olmayacağını merak ediyorum
    O zaman çeviri garantisi bozulur ama ikili taşınabilirliği pratikte daha iyi hale gelebilir

    • Emülasyona kıyasla performans farkının daha iyi olacağı söylenemez
      Bu çevirici Box64 veya FEX’ten çok daha yavaş ve herhangi bir nedenle JIT kullanamayan bir durumda değilseniz, düpedüz daha kötü bir seçenek
  • Çeviricinin dolaylı atlama işlemlerini nasıl ele aldığını hep merak etmişimdir
    Bir ikiliyi analiz ederken yalnızca hedef adresi bilinen doğrudan atlamalarla bağlı kod bölümlerini keşfedebilirsiniz
    Bu durumda her dolaylı atlamada hedef işlevi bulup, gerekirse çevirip sonra çevrilmiş koda geri dönmek gerekir; bu yavaş olmaz mı?
    Daha hızlı bir yöntem olup olmadığını, çevrilmiş işlev adreslerinin özgün işlev adresleriyle eşleştirilip eşleştirilemeyeceğini ya da özgün adrese çevrilmiş koda giden bir atlama yerleştirilip yerleştirilmediğini merak ediyorum

    • Benim yaptığım çevirici hobi düzeyinde ama “adres X’e dolaylı jmp yapılırsa karşılık gelen blok Y konumundadır” diyen büyük bir tablo tutuyor
      Bu yöntem, tablo kullanmayan doğrudan jmp’den daha yavaş, ama özgün programda da dolaylı atlamalar zaten daha yavaştır ve genelde performans açısından kritik döngüler içinde sık görülmez
  • Üstküme kontrol akışı grafiği fikrini gerçekten seviyorum, ancak yazıyı okumayı düşünenlerin şunları bilmesinde fayda var
    Çalışma süresi yaklaşık 4,75 kat hızlanıyor (QEMU’dan hızlı ama Box64’ten belirgin biçimde yavaş), yürütülen komut sayısı 7 kat artıyor ve ikili boyutu 50 kat büyüyor
    Dış çağrılara kadar x86 ABI emüle ediliyor
    EFLAGS gibi x86 CPU durumunun büyük bir bölümü emüle edilmek zorunda ve karmaşık mov işlemleri de tek tek hesaplanmalı
    Yalnızca tek iş parçacıklı ikililer destekleniyor
    İstisna işleme ve yığın çözme (unwinding) yok
    Tüm komut kümesi desteklenmiyor

  • İlginç bir çalışma
    Ayrıntılı bakmadım ama göreli ofsetler hâlâ sorun olabilir gibi geliyor
    Sonuçta üretilen kodun boyutu farklı olacağından, bir tür çeviri katmanı ya da MMU gerekebilir; bunun da daha çok atlama tablolarını ve iç dallanmaları etkilemesi muhtemel
    Ben daha çok 90’lardan kalma şeylerle uğraşıyorum ve ters assembler’lar kodun başlangıcı ve sonu hakkında çok sayıda varsayım yapıyor
    Ama bazen sabit konumdaki giriş noktası işaretçileri gibi ön bilgi olmadan ikili parçasını bulmak mümkün olmuyor
    Birkaç geçişten sonra ikiliyi “kesinlikle kod olan alanlar”a indirgemek mümkün olabilir gibi görünüyor

  • Eğer “Elevator her baytın tüm olası yorumlarını dikkate alıyor, her olası yorum için ayrı çeviriyi önceden üretiyor ve [...] yalnızca çöküşe götüren durumları buduyorsa”, çakışma ihtimali taşıyan gerçek programların hepsi budanmış olmuyor mu?

    • Muhtemelen adres→kod arama tablosunda bunu standartlaştırılmış bir çakışma yoluna ayarlıyordur
      Böylece yine çakışma olur ama doğrudan yürütülen hatalı kodun çakışmasıyla aynı şey olmaz
  • Benim için en ilginç kısım sertifikasyon açısından
    Havacılık ve tıbbi cihazlar gibi regülasyona tabi sektörlerde çalışan kodun sertifikalı kod olması gerekiyor; tam da bu yüzden JIT çoğu zaman kullanılamıyor
    İmzalanabilir bir ikili üreten statik çeviri, kod şişmesini göze alsanız bile pratik bir atılım olabilir

    • Yazılım sektöründe bu alanın ne kadar büyük olduğunu merak ediyorum
      Muhtemelen burada LLM’leri de büyük ölçekte uygulamanın bir yolu yoktur ama “iş dünyasında AI” gibi büyük anlatılarda bu kısım neredeyse hiç ele alınmıyor
  • 50 kat makul değil, tam bir önbellek felaketi
    JIT’ten kaçınarak elde edilen tüm performans kazancı ortadan kalkabilir

    • Bu ancak çalışma sırasında o kodun tamamı gerçekten kullanılırsa geçerli; olası çözümleme başlangıç noktalarının büyük kısmı muhtemelen hiç kullanılmayacaktır
    • Bu, bağlama zamanında kod yeniden yerleşimi için çok uygun bir örnek
      Sıcak kod bir araya toplanırsa kullanılmayan kodun hiç yüklenmemesi sağlanabilir
    • Hemen hüküm vermem
      Komutlar sonuçta o kadar da büyük değil ve CPU çalışma sırasında zaten optimizasyon yapıyor
  • Kendini değiştiren kodu işleyebiliyor mu?
    Ayrıca neden yalnızca x86_64 olduğunu da merak ediyorum
    Eski oyunlar gibi 32 bit programları çevirmek daha anlamlı görünüyor

    • Bağlantı verilen yazıyı okursanız bu konu açıkça ele alınıyor
      “Kendini değiştiren ve JIT derlenmiş kod. Elevator, tüm tamamen statik ikili yeniden yazıcılar gibi, kendini değiştiren kodu veya JIT derlenmiş kodu desteklemez”
    • JIT çalışma zamanı dışındaki kendini değiştiren kod, bugünlerde 80’ler ve 90’lara kıyasla oldukça nadir bence
      Günümüzde .text bölümü çoğunlukla salt okunur ve güvenlik gereksinimlerinin azalması da beklenmez
    • Kendini değiştiren kodu ele alırsanız artık “tamamen statik” olmaz
      Bu temelde bir çelişki
    • Yeni x86 geliştiren biri açısından bakarsak, kendini değiştiren kod mümkün olsa da genelde korkunçtur
      Çünkü önbellek satırlarını ve ardışık düzen dallanma tahmini performansını bozar
      Ayrıca W^X’i ihlal eder, bu yüzden genelde yalnızca JIT uyumlu bellek sayfalarında kullanılmalıdır
      Bu nedenle neredeyse her zaman kaçınılması gerekir
      486 veya P5 döneminde, sabit değerleri iç döngü değişkeni gibi kullanmak için bir ölçüde görülüyordu ama artık pek öyle değil
      Neredeyse kusursuz emülasyon ya da çeviri elde etmek istiyorsanız, x86’in ele alınması gereken çok sayıda kirli istisna durumu var
  • Kaynak kodu nerede?