1 पॉइंट द्वारा GN⁺ 1 시간 전 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • Elevator debug जानकारी, source code, या binary layout के बारे में किसी भी अनुमान के बिना पूरे x86-64 executable को स्थिर रूप से AArch64 में अनुवाद करता है
  • code और data को अलग करने वाली हीयूरिस्टिक्स की जगह, यह हर byte की सभी संभावित व्याख्याओं को शामिल करने वाला superset CFG बनाता है और केवल उन paths को हटाता है जो असाधारण program termination तक जाते हैं
  • x64 state को AArch64 registers में one-to-one map किया जाता है, और indirect branches को original addresses से translated code तक ले जाने वाली lookup table से संभाला जाता है
  • offline tile bank x64 instruction semantics को C templates में लिखकर फिर LLVM 20 से AArch64 byte sequences में compile करता है
  • परिणाम एक self-contained AArch64 binary होता है जिसमें runtime translation नहीं होता, और SPECint 2006 में यह QEMU user-mode JIT के बराबर या उससे बेहतर performance देता है

Elevator का लक्ष्य

  • Elevator एक पूरी तरह static binary translator है जो पूरे x86-64 executable को AArch64 में ले जाता है
  • यह debug information, source code, original binary के code patterns, या binary layout के बारे में कोई अनुमान इस्तेमाल नहीं करता
  • मौजूदा static translators code और data को अलग करने के लिए हीयूरिस्टिक्स या runtime fallback पर निर्भर रहते हैं, लेकिन Elevator original executable के हर byte को उसकी सभी संभावित व्याख्याओं के अनुसार पहले से अनुवाद करता है
  • कोई भी byte data, opcode का हिस्सा, या opcode argument का हिस्सा हो सकता है, इसलिए यह सभी संभावित control flow को शामिल करने वाला superset CFG बनाता है और केवल उन paths को हटाता है जो असाधारण program termination तक जाते हैं
  • output एक self-contained AArch64 binary होता है जिसमें translated code, original x64 binary, address lookup table, और runtime driver शामिल होते हैं
  • translation पूरा होने के बाद इसे JIT या runtime translation support के बिना चलाया जा सकता है
  • एक ही input binary को दो बार translate करने पर वही output bitstream बनती है, इसलिए testing, verification, certification, और cryptographic signing का लक्ष्य वास्तविक deployed code से मेल खाता है
  • मुख्य लागत code size में वृद्धि है, लेकिन इसके बदले emulator या JIT compiler की तुलना में deployment से पहले verification की संभावना बढ़ जाती है
  • evaluation में पूरा SPECint 2006 benchmark suite और हाथ से बनाए गए binaries शामिल थे, और performance JIT acceleration वाले QEMU user-mode emulation के बराबर या उससे बेहतर रही
  • शोधकर्ताओं ने कहा है कि project पूरा होने पर वे इसे पूरी तरह open source के रूप में जारी करेंगे

static translation की ज़रूरत और मौजूदा सीमाएँ

  • जब hardware एक ISA से दूसरे ISA पर जाता है, तब पुराने software को नए platform पर लाना पड़ता है, और बचे हुए source code को फिर से compile करना हमेशा पर्याप्त नहीं होता
  • verified या certified legacy code में अक्सर source code नहीं बल्कि अच्छी तरह test किया गया एक खास authoritative binary executable ही certification का लक्ष्य होता है
  • बाद में source से बिल्कुल वही binary bit-for-bit दोबारा बनाने के लिए उस समय के compiler, linker, और build system versions की ज़रूरत पड़ सकती है, जो व्यवहार में मुश्किल है
  • अगर manufacturer ने source code के बिना सीधे binary पर patches लगाए हों, तो archived source से rebuild करने पर पहले से ठीक की गई bugs फिर से लौट सकती हैं
  • मौजूदा binary-direct approaches emulation, static translation, और dynamic translation का मिश्रण करती हैं, लेकिन translated program के साथ चलने वाले अतिरिक्त system components trusted computing base में शामिल हो जाते हैं
  • dynamic behavior test order या inputs के अनुसार बदल सकता है, इसलिए संपूर्ण विश्वसनीयता की पुष्टि करना कठिन हो जाता है
  • Horspool और Marovac ने 1980 में दिखाया था कि executable को reverse-transform करने के लिए code और data को निश्चित रूप से अलग करना ज़रूरी है, और अधिकांश architectures में यह halting problem के बराबर है, इसलिए सामान्य रूप से हल नहीं किया जा सकता
  • मौजूदा static binary lifters code और data के फर्क को हीयूरिस्टिक्स से approximate करते हैं, और खासकर indirect control-flow transfer के targets का अनुमान लगाते समय समस्या बढ़ जाती है
  • LLBT ARM instructions को LLVM IR में उठाकर target architecture के लिए recompile करता है, लेकिन indirect branch target detection के लिए हीयूरिस्टिक्स का उपयोग करता है और input binary के बारे में कई assumptions रखता है
  • अच्छी हीयूरिस्टिक्स भी कुछ inputs पर विफल होती हैं, और पूरे binary को सही ढंग से lift करने के लिए code/data classification हर जगह सही होना चाहिए, इसलिए binary जितना बड़ा होगा failure की संभावना उतनी बढ़ेगी
  • dynamic methods वास्तव में execute हुए instruction flow को follow करती हैं, इसलिए वे instruction recovery और indirect control flow संभाल सकती हैं, लेकिन concrete execution में जिन instructions तक पहुँचा ही नहीं गया उन्हें lift नहीं कर सकतीं
  • x64 जैसी variable-length instructions वाली ISA में एक instruction sequence के भीतर दूसरी instruction sequence nested हो सकती है, और multi-byte instruction के बीच में branch होने पर मौजूदा operands अलग instructions की तरह decode हो सकते हैं
  • ROP attacks और code obfuscation इस गुण का उपयोग कर सकते हैं
  • Apple का Rosetta II और Microsoft का Prism pre-translation और dynamic translation components को जोड़ते हैं
  • WYTIWYG और Polynima dynamic profiling से पहचाने गए control-flow paths के साथ static lifting करते हैं, और जब किसी अनदेखे target address तक पहुँचा जाता है तो dynamic fallback से control-flow जानकारी इकट्ठा करते हैं
  • Elevator यह तय नहीं करता कि कौन-सा byte code है या data, instruction word है या argument; इसके बजाय executable के हर byte को उसकी सभी संभावित व्याख्याओं के साथ अलग control-flow paths में शामिल करता है
  • यह तरीका superset disassembly को static recompilation और cross-ISA compilation पर लागू करता है, जहाँ decode precision की कीमत code growth से चुकाई जाती है

control flow और state preservation

  • Elevator translated AArch64 code के अंदर पूरे x64 state को संरक्षित रखने के सिद्धांत पर काम करता है
  • x64 registers और AArch64 registers को one-to-one map करके हर x64 register state को संबंधित AArch64 register में emulate किया जाता है
  • x64 stack को सीधे AArch64 stack के ऊपर emulate किया जाता है, और execution के दौरान सामान्य stack growth को operating system संभालता है
  • input x64 binary के ABI का विश्लेषण किए बिना, केवल उन बिंदुओं पर ABI translation किया जाता है जहाँ execution external code में जाती है या वहाँ से लौटती है; यह x64 System V ABI और AArch64 Procedure Call Standard के नियमों के अनुसार होता है
  • complete state preservation और one-to-one register mapping की वजह से हर x64 instruction को उसके पहले या बाद के instructions जाने बिना स्वतंत्र रूप से translate किया जा सकता है
  • original binary का हर executable byte offset एक साथ data और संभावित instruction sequence के start point के रूप में व्याख्यायित होता है
  • indirect jumps, callbacks, और runtime dispatch जैसे सभी संभावित targets जिन्हें static रूप से analyze नहीं किया जा सकता, rewritten binary में corresponding landing points पाते हैं
  • runtime पर original instruction address से translated code address तक जाने के लिए अंतिम binary में शामिल lookup table का उपयोग किया जाता है
  • nested instruction का उदाहरण

    • Listing 1 में .byte 0xB0 से decode शुरू करने पर MOV AL, 0xC3 के बाद RET आता है, जबकि एक byte बाद ReturnC2 से शुरू करने पर केवल RET मिलता है
    • दोनों decodes पहले वाले jz से पहुँचे जा सकते हैं, और अगर translator इन दो bytes के लिए सिर्फ एक व्याख्या चुने तो एक path छूट जाएगा
  • computed indirect branch का उदाहरण

    • Listing 2 में call Label table-relative address बनाता है, pop rsi उससे address वापस लेता है, फिर input-dependent offset जोड़कर jmp rsi का target बनाता है
    • branch encoding stream में 2-byte अंतर पर रखे गए चार inc eax instructions में से किसी एक पर land कर सकती है
    • जो translators केवल statically resolvable jump targets को rewrite करते हैं, उनके पास ऐसी branch के landing points नहीं होंगे
  • call·return·branch

    • call, return, और branch instructions को C tiles में व्यक्त नहीं किया जा सकता क्योंकि return address location, program counter, और condition flag layout x64 और AArch64 में अलग होते हैं
    • direct call original x64 return address को emulated stack पर push करती है और callee के translated tile पर branch करती है
    • indirect call यह जाँचती है कि target translated binary के अंदर है या external library में; internal target को x64 offset-to-tile table से translate करके उसी tile पर branch किया जाता है
    • external target के लिए AArch64 library के लौटने वाले X30 में reverse ABI translation gadget address रखा जाता है, exit ABI translation किया जाता है, और फिर external target पर branch किया जाता है
    • return emulated stack से 8-byte return address निकालती है, उसे embedded x64 binary range से compare करती है, और अगर return internal हो तो lookup table से address translate करके संबंधित tile पर branch करती है
    • direct branches के targets translation समय पर ज्ञात होते हैं, और conditional branches को X14 में रखे x64 flag bits की जाँच करने वाली AArch64 conditional branches में translate किया जाता है
    • indirect branches indirect calls और returns की तरह bounds check emit करती हैं, और target external हो तो exit ABI translation करती हैं

tile-आधारित translation pipeline

  • Elevator का translation तीन चरणों में बँटा है: offline tile bank generation, input-binary-specific rewriting, और final packaging
  • offline चरण x64 instruction semantics को C functions के रूप में व्यक्त करता है, fixed x64-to-AArch64 register mapping के तहत operand combinations के हिसाब से specialize करता है, और modified LLVM 20 से compile करके reusable AArch64 byte sequences बनाता है
  • per-binary चरण superset disassembly करता है, और हर candidate instruction के लिए नाम के आधार पर tile ढूँढकर AArch64 byte sequences को जोड़ता है
  • control-flow transfers और ABI boundaries जैसी instruction categories जिन्हें C tiles में व्यक्त करना कठिन है, उन्हें हाथ से बने छोटे templates से संभाला जाता है
  • packaging चरण translated code, original x64 binary, address lookup table, और runtime driver को जोड़कर standalone executable AArch64 binary बनाता है
  • offline tile bank

    • हर x64 instruction के लिए बराबर AArch64 instruction sequence हाथ से लिखना व्यावहारिक नहीं है
    • ADD Reg8, Reg8 जैसे एक template को भी 256 ठोस register combinations तक फैलाया जा सकता है, और पूरे x64 instruction set में register, memory operand, और immediate addressing के बहुत से variants होते हैं
    • Elevator हर x64 instruction semantics को छोटे C function में लिखता है, फिर उसे ठोस operand combinations के अनुसार specialize करता है, और LLVM से AArch64 में compile करवाता है
    • ADD Reg8, Reg8 उदाहरण में template destination register के निचले 8 bits को 8-bit sum से update करता है और ऊपरी 56 bits को वैसे ही रखता है, ताकि x64 के partial register write semantics मेल खाएँ
    • x64 ADD Reg8, Reg8 RFLAGS के Carry, Parity, Auxiliary Carry, Zero, Sign, और Overflow flags भी बदलता है, इसलिए single return value वाले C function की सीमा के कारण flag updates को अलग flag tiles में capture किया जाता है
    • एक x64 instruction एक या कई tiles से मेल खा सकती है, और emit करते समय इन्हें फिर लगातार जोड़कर पूरा semantics बहाल किया जाता है
    • aarch64_custom_reg attribute यह घोषित करता है कि LLVM return value और हर argument को किस AArch64 register में रखेगा
    • fixed mapping इस तरह चुनी गई है कि x64 System V और AAPCS64 के callee-saved/caller-saved गुण मेल खाएँ, integer argument registers की rearrangement कम हो, और बचे हुए AArch64 callee-saved registers भविष्य के shadow state के लिए उपलब्ध रहें
    • x64 के RFLAGS bits और XMM register file भी इसी one-to-one principle के तहत dedicated AArch64 registers में रखे जाते हैं
    • modified LLVM 20 per-function aarch64_custom_reg attribute को संभालता है और emulated x64 state रखने वाले AArch64 registers को register allocator के भीतर callee-saved के रूप में पुनर्वर्गीकृत करता है
    • TileGen C templates पर चलकर हर allowed operand combination के लिए specialized copies बनाता है, और template की parameter positions और register mapping से attributes को यांत्रिक रूप से synthesize करता है
  • input-binary-specific rewriting

    • input x64 binary दिए जाने पर per-binary चरण superset disassembly करता है और resulting CFG को traverse करता है
    • हर node पर formatter decoded instruction के opcode और operands से tile name बनाता है, और जिन instructions को कई tiles चाहिए उनके लिए कई names जोड़ता है
    • x64 में stack pointer alignment की बाध्यता नहीं होती, लेकिन AArch64 में memory operands के साथ stack pointer उपयोग करते समय 16-byte alignment चाहिए
    • अगर RSP को सीधे SP पर map कर दिया जाए, तो function prologue की लगातार PUSH जैसी सामान्य x64 code patterns AArch64 में alignment exception पैदा कर सकती हैं
    • Elevator tiles को अलग register X25 के ज़रिए stack तक पहुँचने देता है, और केवल तब SP को उसमें materialize करता है जब tile को सच में इसकी ज़रूरत हो
    • LLVM से compiled tiles entry पर 16-byte SP alignment की उम्मीद करते हैं, इसलिए जिन tiles में spill space allocation detect होती है, उन्हें चलाने से पहले SP को नीचे align किया जाता है और execution के बाद बहाल किया जाता है
    • क्योंकि flag computation tiles अपेक्षाकृत महँगे हैं, अगर flags बाद के post-dominating instructions द्वारा पढ़े जाने से पहले overwrite हो जाते हैं तो current node की flag computation हटा दी जाती है
    • अभी unsupported instructions मुख्य रूप से x64 की AVX2 और बाद की wide vector extensions हैं, और उन जगहों पर tiles की बजाय interrupt instructions डाली जाती हैं
    • पूरे SPECint 2006 evaluation में पूर्ण x86-64 integer ISA और SPECint द्वारा उपयोग किए जाने वाले SSE subset से सभी benchmarks चलाने के लिए पर्याप्त coverage मिली
    • अतिरिक्त instruction support नए tiles जोड़कर बढ़ाई जा सकती है, लेकिन लेखकों का मानना है कि इससे अतिरिक्त engineering तो होगी पर वैज्ञानिक अंतर्दृष्टि सीमित ही बढ़ेगी

ABI boundary handling

  • Elevator केवल dynamically linked binaries को support करता है
  • statically linked binaries में CPUID जैसे architecture-specific instructions सीधे शामिल हो सकते हैं, लेकिन dynamically linked binaries इन्हें libc को सौंप देती हैं, इसलिए translation की ज़रूरत कम हो जाती है
  • dynamic linked libraries के साथ interact करते समय emulated x64 environment और native AArch64 library code के बीच आने-जाने के लिए x64 Linux ABI और AArch64 Linux ABI के बीच transition support दिया जाता है
  • ABI translation के लिए मुख्य तत्व argument placement और return address की position हैं
  • System V x64 ABI RDI, RSI, RDX, RCX, R8, R9 इन छह registers को argument registers के रूप में इस्तेमाल करता है, और अतिरिक्त arguments को [RSP+8] से stack पर भेजता है
  • x64 CALL return address को [RSP] में store करती है
  • AArch64 Procedure Call Standard X0-X7 आठ argument registers का उपयोग करता है, बाकी arguments को [SP] के stack पर रखता है, और return address को X30 में store करता है
  • external library calls

    • जब translated x64 call किसी external library को target करती है, तो arguments layout को AArch64 calling convention के अनुसार बदलना पड़ता है
    • पहले SP से 8 घटाकर उसे फिर 16-byte boundary पर align किया जाता है, और पहले से stack पर मौजूद x64 return address को [SP+0x8] पर रखा जाता है
    • [SP+0x10] और [SP+0x18] के values को X6, X7 में load किया जाता है ताकि x64 code ने stack पर रखे संभावित 7वें और 8वें arguments को AArch64 library देख सके
    • बाकी संभावित stack arguments [SP+0x20] से बने रहते हैं, जो AArch64 की अपेक्षित positions से मेल नहीं खाते
    • x64 return address और X6, X7 में ले जाए गए values को stack से हटाना सुरक्षित नहीं है, क्योंकि ये वास्तविक arguments न होकर caller spill space या caller stack पर रखी गई किसी structure का हिस्सा भी हो सकते हैं
    • Elevator caller के stack layout को छेड़े बिना n×8 bytes की अतिरिक्त stack space allocate करता है, और current location से n संभावित 8-byte arguments की copy करता है
    • default n 10 है, और अगर input binary किसी external library function को कुल 16 से अधिक arguments देता है तो इसे configuration से बढ़ाया जा सकता है
    • अंत में external library के लौटने वाले gadget का address X30 में store किया जाता है
  • external library से लौटना

    • जब control external library call से पहले X30 में रखे गए gadget पर वापस आता है, तब पहले copy किए गए stack arguments साफ करने के लिए stack pointer में n×8 जोड़ा जाता है
    • external library का return value X0 से emulated x64 code द्वारा अपेक्षित RAX location यानी X9 में move किया जाता है
    • original x64 return address और उससे जुड़ी padding को stack से निकाला जाता है, address को translate किया जाता है, और वहीं branch करके original CALL के बाद execution फिर शुरू होती है
  • translated code में आने वाले callbacks

    • जब native AArch64 code translated binary को call करती है, तब AArch64 calling convention को x64 calling convention में बदलना पड़ता है
    • emulated x64 code 7वें और 8वें arguments को X6, X7 में नहीं बल्कि stack पर अपेक्षित करती है, इसलिए X7 को पहले push किया जाता है और फिर X6 को, ताकि वे x64 की अपेक्षित stack positions पर आ जाएँ
    • अगर callee वास्तव में 7वें और 8वें arguments की अपेक्षा नहीं करती, तो इन pushed values का कोई प्रभाव नहीं पड़ता
    • external library की AArch64 branch-and-link instruction ने X30 में जो return address रखा, उसे x64 return instruction द्वारा अपेक्षित stack position पर push किया जाता है
  • callback से external library को लौटना

    • जब translated code callback से external library में लौटती है, तो entry process को उल्टा किया जाता है
    • return address को stack से pop किया जाता है, X6 और X7 को push किया जाता है, और allocated stack space को stack pointer में 0x10 जोड़कर साफ किया जाता है

1 टिप्पणियां

 
GN⁺ 1 시간 전
Hacker News टिप्पणियाँ
  • मुझे ठीक-ठीक नहीं पता कि QEMU का user-mode JIT असल में क्या करता है, लेकिन इसमें सुधार की काफी गुंजाइश दिखती है
    2013 में मैंने x86-64 से aarch64 में बदलने वाला JIT engine बनाया था, और उस समय Fedora beta aarch64 binaries चलाकर x86_64 Linux पर Fedora के aarch64 port का ज़्यादातर हिस्सा दोबारा build कर सका था
    उल्टी दिशा में aarch64 → x86-64 JIT भी बनाया था, और मज़े के लिए यह भी दिखाया था कि एक ही process के अंदर x86-64 → aarch64 → x86_64 तरह दोनों JIT एक-दूसरे को loopback की तरह चला सकते हैं
    मेरे बनाए JIT में instructions और CPU state का 1-to-many mapping था, और यह native recompiled code की तुलना में लगभग 2~5 गुना धीमा था
    बाद में जब QEMU JIT से तुलना की, तो QEMU लगभग 10~50 गुना धीमा दिखा
    अफ़सोस, open source license व्यवस्था नहीं थी, इसलिए इसे साबित करने वाला code सार्वजनिक नहीं कर सका

    • सही बात है, QEMU JIT को हराना काफ़ी आसान लक्ष्य के करीब है
      खासकर अगर design को “सिर्फ x86 से aarch64” और “सिर्फ user mode” के लिए specialize किया जा सके, तो performance gain बहुत मिल सकता है
      QEMU का user-mode support system emulation support से जुड़ा एक “किसी तरह चल जाने वाला” appendix जैसा है, और पूरा JIT structure भी “guest → intermediate representation → host” तरीके का है, जो कई guest architectures और कई host architectures को support करने के लिए अच्छा है, लेकिन “x86 में integer registers कम हैं इसलिए hard allocation किया जा सकता है” या “aarch64 CPU को सही mode में रखा जाए तो complex floating-point semantics हमेशा सही बैठते हैं” जैसी खास guest/host जोड़ी की विशेषताओं का फायदा उठाना मुश्किल है
      ऊपर से QEMU development में performance optimization के मौके ढूँढने से ज़्यादा समय “नई architecture feature X को emulate करना” में जाता है, क्योंकि development cost देने वालों के लिए वही ज़्यादा महत्वपूर्ण है
    • QEMU translator से ज़्यादा TCG है, और n architectures पर चलने के लिए design किया गया है, इसलिए इसकी सीमाएँ हैं
  • .text section का 50 गुना बड़ा होना बहुत भारी है, लेकिन पूरी तरह deterministic translation पाने की कीमत के तौर पर यह स्वीकार्य लगता है
    कई मामलों में size increase की असुविधा से ज़्यादा फ़र्क emulation की तुलना में performance difference से पड़ेगा
    यह भी दिलचस्प है कि multithreading और exception handling असंभव नहीं हैं, बस इस project के scope के बाहर हैं
    सोच रहा हूँ कि अगला step क्या heuristics से possibility space को काटकर binary size घटाना होगा
    तब translation guarantee टूट जाएगी, लेकिन binary portability व्यावहारिक रूप से बेहतर हो सकती है

    • यह ज़रूरी नहीं कि emulation की तुलना में performance difference ज़्यादा ही हो
      यह translator Box64 या FEX से काफ़ी धीमा है, और अगर किसी वजह से भी JIT इस्तेमाल करने से रोका न गया हो, तो यह बस बदतर विकल्प है
  • मैं हमेशा सोचता था कि translator indirect jumps को कैसे handle करता है
    binary का analysis करते समय तो सिर्फ direct jumps से जुड़े code regions ही मिल सकते हैं, जहाँ destination address पता हो
    फिर क्या इसका मतलब है कि indirect jump हर बार होने पर target function ढूँढना पड़ेगा, ज़रूरत हो तो translate करना पड़ेगा, और फिर translated code में लौटना पड़ेगा? क्या यह धीमा नहीं होगा?
    सोच रहा हूँ कि क्या इससे तेज़ कोई तरीका है, translated function addresses को original function addresses से match किया जा सकता है, या original address पर translated code तक जाने वाला jump डाला जाता है

    • मेरा translator hobby-level का है, लेकिन उसमें एक बड़ी table रहती है कि “address X पर indirect jmp हो तो corresponding block location Y पर है”
      यह तरीका table न इस्तेमाल करने वाले direct jmp से धीमा है, लेकिन original program में भी indirect jumps वैसे ही धीमे होते हैं, और आमतौर पर performance-critical loops के अंदर बार-बार नहीं आते
  • superset control flow graph का idea मुझे बहुत पसंद आया, लेकिन जो लोग लेख पढ़ना चाहते हैं, उनके लिए नीचे की बातें जानना उपयोगी होगा
    execution time लगभग 4.75 गुना तेज़ होता है (QEMU से तेज़, लेकिन Box64 से काफ़ी धीमा), executed instructions की संख्या 7 गुना बढ़ती है, और binary size 50 गुना बढ़ती है
    external calls तक x86 ABI को emulate करता है
    EFLAGS जैसी x86 CPU state का बड़ा हिस्सा emulate करना पड़ता है, और complex mov भी अलग-अलग गिनना पड़ता है
    सिर्फ single-threaded binaries supported हैं
    exception handling और stack unwinding नहीं है
    पूरा instruction set support नहीं होता

  • दिलचस्प काम है
    मैंने इसे विस्तार से नहीं देखा, लेकिन relative offsets अब भी समस्या हो सकते हैं
    क्योंकि code generation के नतीजे का size अलग होगा ही, इसलिए किसी तरह की translation layer या MMU चाहिए होगी, और इसका असर मुख्यतः jump tables और internal branches पर पड़ेगा
    मैं ज़्यादातर 90s की चीज़ों से डील करता हूँ, और disassemblers code की शुरुआत और अंत को लेकर बहुत assumptions रखते हैं
    लेकिन कभी-कभी fixed-position entry point pointers जैसी prior knowledge न हो, तो binary chunks का पता ही नहीं चलता
    कुछ passes के बाद binary को “जहाँ पक्का code है” ऐसे regions में refine किया जा सकता है

  • अगर “Elevator हर byte की हर संभावित interpretation पर विचार करता है, हर संभावना के लिए अलग translation पहले से बनाता है, और [...] सिर्फ crash तक ले जाने वाले मामलों को prune करता है”, तो क्या collision की संभावना वाले असली programs सब prune हो जाते हैं?

    • शायद address→code lookup table में इसे standardized collision path पर सेट किया जाएगा
      तब collision तो होगा, लेकिन वह सीधे execute हुए गलत code के crash जैसा नहीं होगा
  • मेरे लिए सबसे दिलचस्प हिस्सा certification का angle है
    aviation, medical devices जैसे regulated industries में अक्सर चलने वाला code certified code ही होना चाहिए, और ठीक इसी वजह से कई बार JIT इस्तेमाल नहीं किया जा सकता
    sign की जा सकने वाली binaries बनाने वाला static translation, code bloat के बावजूद, व्यावहारिक breakthrough हो सकता है

    • सोचता हूँ software industry में यह क्षेत्र कितना बड़ा है
      शायद यहाँ LLM को भी बड़े पैमाने पर लागू करने का रास्ता नहीं होगा, लेकिन “काम में AI” वाली बड़ी बहस में ऐसे हिस्सों की लगभग चर्चा ही नहीं होती
  • 50 गुना उचित नहीं है, यह cache disaster है
    JIT से बचकर जो performance gain मिलेगा, वह पूरा खत्म हो सकता है

    • वास्तविक runtime में तभी ऐसा होगा जब वह सारा code सच में इस्तेमाल हो, जबकि संभावित decoding start points का ज़्यादातर हिस्सा शायद कभी इस्तेमाल ही न हो
    • यह link-time code relocation के लिए बहुत उपयुक्त केस है
      अगर hot code को एक जगह इकट्ठा कर दिया जाए, तो unused code कभी load ही न हो ऐसा बनाया जा सकता है
    • मैं इतनी जल्दी निष्कर्ष पर नहीं पहुँचूँगा
      instructions वैसे भी इतने बड़े नहीं होते, और CPU चलते समय optimization भी करता है
  • क्या यह self-modifying code को handle कर सकता है?
    और यह सिर्फ x86_64 के लिए ही क्यों है?
    पुराने games जैसे 32-bit programs को translate करना ज़्यादा अर्थपूर्ण लगता है

    • linked article पढ़ें तो यह हिस्सा साफ़ तौर पर बताया गया है
      “Self-modifying और JIT-compiled code. Elevator, बाकी सभी fully static binary rewriters की तरह, self-modifying code या JIT-compiled code को support नहीं करता”
    • JIT runtime के बाहर का self-modifying code आजकल 80s~90s की तुलना में काफ़ी दुर्लभ है
      आजकल .text section ज़्यादातर read-only होता है, और security requirements के कम होने की कोई संभावना भी नहीं है
    • अगर self-modifying code को handle किया जाए, तो वह फिर “fully static” नहीं रहेगा
      यह बुनियादी तौर पर विरोधाभास है
    • जो लोग नया x86 development करते हैं, उनके नज़रिए से self-modifying code संभव तो है, लेकिन आमतौर पर भयानक होता है
      क्योंकि यह cache lines और pipeline branch prediction performance को बिगाड़ देता है
      साथ ही यह W^X का उल्लंघन करता है, इसलिए आमतौर पर इसे सिर्फ JIT-compatible memory pages में ही लिखना चाहिए
      इसलिए लगभग हमेशा इससे बचना चाहिए
      486 या P5 के समय में इसे कभी-कभी ऐसे इस्तेमाल किया जाता था कि immediate values को inner loop variables की तरह रखा जाए, लेकिन अब ऐसा कम ही होता है
      लगभग परिपूर्ण emulation या translation हासिल करने के लिए x86 के कई गंदे edge cases को handle करना पड़ता है
  • source code कहाँ है?