1 poin oleh GN⁺ 2024-07-11 | 1 komentar | Bagikan ke WhatsApp
  • Dalam proses menulis ulang emulator CPU untuk Time Travel Debugging dengan C++, x86/amd64 perlu menangani perilaku yang sama secara berbeda bergantung pada encoding, prefix, dan mode eksekusi
  • Ada banyak representasi alternatif yang memengaruhi performa dan debugging, seperti encoding satu byte CC untuk int 3, bentuk singkat ADD EAX, imm, dan prefix REX yang tidak berdampak
  • Instruksi INC/DEC, CMPXCHG8B/CMPXCHG16B, serta shift/rotate mudah berujung pada bug emulator karena cara penanganan flag-nya tidak intuitif
  • Shift count tidak diterapkan apa adanya sesuai ukuran operand, melainkan di-mask; karena itu shr eax,20h tidak membuat register 32-bit menjadi 0, tetapi mempertahankan nilainya
  • Segment masih digunakan untuk akses TEB di Windows 32-bit dan 64-bit, dan perbedaan makna FS/GS serta cara penentuan base berdampak langsung pada implementasi disassembler dan emulator

Aturan detail x86 yang terungkap saat menulis ulang emulator TTD

  • Salah satu komponen Time Travel Debugging adalah emulator CPU yang merekam seluruh eksekusi proses pada tingkat instruksi
  • Emulator pada versi pertama, iDNA, hampir seluruhnya ditulis dalam assembly sehingga cepat, tetapi sulit dipelihara dan diperluas
  • Pada versi kedua, bagian emulasi dan kemudian sebagian besar bagian lain ditulis ulang dalam C++, dengan tujuan mempertahankan sebagian besar performa versi assembly sambil memiliki basis kode yang lebih mudah dikelola
  • Untuk membuat emulator CPU, detail perilaku CPU harus dicocokkan tanpa terlewat; bahkan aturan yang akrab bagi orang berpengalaman dengan x86 pun harus diverifikasi ulang saat diimplementasikan secara nyata

Encoding x86 yang mengekspresikan instruksi yang sama dengan berbagai cara

  • x86 dapat mengekspresikan instruksi yang sama sebagai beberapa rangkaian byte
  • int 3 dapat di-encode sebagai CD 03, tetapi juga dapat di-encode sebagai CC satu byte
    • Karena digunakan sebagai breakpoint perangkat lunak, breakpoint dapat dipasang bahkan pada posisi instruksi di akhir halaman memori ketika halaman berikutnya tidak dipetakan
  • Ada juga encoding alternatif untuk mempersingkat kasus umum
    • add eax, imm dapat direpresentasikan secara singkat seperti 05cccccccc
    • Untuk menambahkan nilai yang sama ke ECX, diperlukan 1 byte tambahan, seperti 81c1cccccccc
  • EAX disebut “Accumulator register” bukan sekadar karena konvensi, tetapi karena benar-benar menimbulkan perbedaan encoding; instruksi pendek dapat mengurangi data yang harus dipindahkan dari memori utama dan penggunaan cache instruksi, sehingga bisa menguntungkan performa
  • Compiler dapat memanfaatkan encoding pendek seperti ini jika memungkinkan

Prefix dan batas panjang instruksi 15 byte

  • Instruksi x86 dapat memiliki byte prefix yang mengubah perilakunya
  • Prefix REX yang umum dipakai pada kode 64-bit digunakan untuk mengakses rentang register yang lebih luas daripada kode 32-bit
  • CPU juga menerima prefix REX yang tidak berdampak
    • 4004cc adalah bentuk dengan byte REX di depan add al,0CCh 8-bit, tetapi dalam kasus ini REX tidak memiliki efek apa pun
    • CPU tetap dapat mengeksekusi dua prefix REX yang dipasang berurutan, dan beberapa disassembler, termasuk WinDbg, dapat menjadi bingung karenanya
  • Pada CPU yang kompatibel dengan x86, panjang instruksi saat ini memiliki batas keras 15 byte
    • Instruksi yang melebihi 15 byte dianggap sebagai instruksi tidak valid dan memicu exception
    • CPU lama juga memiliki batasan lain untuk prefix, dan beberapa prefix seperti LOCK memiliki syarat penggunaan yang lebih ketat

Interpretasi yang berubah tergantung ukuran alamat dan mode

  • Prefix address override dapat membuat mode 64-bit merujuk ke alamat 32-bit
    • 488d0424 adalah lea rax,[rsp]
    • 67488d0424 menjadi lea rax,[esp] karena prefix 0x67
  • Pada kode 32-bit, address override mengubah mode alamat menjadi alamat 16-bit
  • Rangkaian byte yang sama pun memerlukan pengetahuan tentang ukuran operand default dan ukuran alamat dari code segment agar dapat di-disassemble atau diinterpretasikan dengan benar
    • 8b0424 dalam mode 32-bit adalah mov eax,dword ptr [esp]
    • 8b0424 dalam mode 64-bit adalah mov eax,dword ptr [rsp]
  • Rentang 40~4F yang digunakan untuk INC reg dan DEC reg di x86 dipakai sebagai byte prefix REX di x64
    • Dalam mode 32-bit, 48 03 04 24 diinterpretasikan sebagai dua instruksi: dec eax dan add eax,dword ptr [esp]
    • Dalam mode 64-bit, 48030424 diinterpretasikan sebagai satu instruksi: add rax,qword ptr [rsp]
  • Perancang AMD64 menggunakan ruang encoding luas milik INC/DEC sebagai prefix baru untuk memperluas set register dalam mode 64-bit, sementara instruksi tersebut sudah memiliki encoding lain yang mendukung register maupun memori

Jebakan mode 64-bit pada INC reg di WinDbg

  • WinDbg selalu meng-assemble instruksi seolah-olah dalam mode 32-bit, sehingga jika mencoba meng-assemble INC reg pada kode 64-bit, hasilnya bisa berbeda dari yang dimaksudkan
  • Dalam contoh, inc eax bukan menjadi instruksi increment yang sebenarnya, melainkan berubah menjadi prefix REX yang tidak berguna yang memodifikasi instruksi berikutnya
  • Akibatnya, rangkaian byte tersebut diinterpretasikan bukan sebagai inc, melainkan sebagai prefix sebelum instruksi jmp

Pengecualian dalam perilaku flag

  • INC EAX tampak mirip dengan ADD EAX, 1, tetapi tidak sepenuhnya sama
    • ADD memperbarui carry flag
    • INC tidak memperbarui carry flag
  • Saat mengimplementasikan emulator TTD, perbedaan ini awalnya diimplementasikan secara keliru, lalu ditemukan melalui unit test
  • Sebagian besar operasi aritmetika dan logika menetapkan overflow, sign, zero, auxiliary carry, parity, dan carry flag
  • CMPXCHG juga menetapkan flag seperti itu, tetapi CMPXCHG8B dan CMPXCHG16B hanya mengubah zero flag
  • Beberapa instruksi membiarkan sebagian flag dalam keadaan tidak terdefinisi
    • Instruksi shift dan rotate membiarkan overflow flag tidak terdefinisi jika shift amount lebih besar dari 1
    • Perilaku aktual flag yang tidak terdefinisi berkaitan dengan implementasi internal operasi shift dan dapat berbeda antar-arsitektur
    • Ada cerita bahwa CPU keluarga Atom melakukan bit shift di ALU dengan cara yang lebih murah dan lebih lambat sehingga nilai flag yang tidak terdefinisi menjadi berbeda, tetapi hal ini tidak diuji langsung

Masking count pada instruksi shift

  • 66c1e810 adalah shr ax,10h, yang menggeser AX ke kanan sebanyak 16 bit
    • Karena AX adalah register 16-bit, hasilnya menjadi 0
  • c1e820 adalah shr eax,20h, dan di permukaan tampak seperti instruksi yang menggeser EAX ke kanan sebanyak 32 bit
  • Namun kenyataannya, nilai EAX tidak berubah
    • Berdasarkan Intel SDM, count di-mask dengan 1Fh, sehingga hanya 5 bit terbawah dari rotasi yang digunakan
    • Jika memakai prefix REX.W, mask menjadi 3Fh, sehingga nilai shift maksimum menjadi 63 bit
  • Perilaku ini pernah benar-benar menjadi masalah dalam pertanyaan wawancara Microsoft tentang “semua cara untuk meng-clear register 32-bit dengan satu instruksi”
    • Pewawancara mengira hal itu bisa dilakukan dengan shift, tetapi jawabannya adalah tidak bisa untuk register 32-bit

Segment yang tetap hidup dalam kode 32-bit dan 64-bit

  • Memori segment mungkin terlihat seperti peninggalan kode 16-bit, tetapi tetap memiliki efek nyata dalam kode 32-bit dan 64-bit
  • Sebagian besar OS menggunakan model memori yang hampir flat dan menaruh segment base address pada 0, sehingga biasanya tidak terlalu disadari
    • Dalam mode 64-bit, CPU selalu memperlakukan segment base CS, DS, ES, dan SS sebagai 0
  • Sebagai pengecualian, thread local storage menggunakan extra segment register seperti FS atau GS
  • Sebagai koreksi, base segment FS/GS juga dapat dibaca oleh kode non-privileged menggunakan instruksi rdfsbase, wrfsbase, rdgsbase, dan wrgsbase
    • Instruksi-instruksi ini tersedia sejak Ivy Bridge, yaitu sejak 2012

Akses TEB Windows dan FS/GS

  • Di Windows, FS dan GS digunakan untuk merujuk ke TEB (Thread Execution Block)
  • Struktur TEB memiliki pointer self yang menunjuk ke flat address pada awal struktur, dan alamat ini juga merupakan base dari segment terkait
  • Pada proses 32-bit, TEB berada di FS
    • GetLastError mengambil TEB.NtTib.Self dari fs:[00000018h], lalu membaca LastErrorValue dari [eax+34h]
  • Pada proses 64-bit, TEB berada di GS
    • GetLastError membaca pointer dari gs:[30h], lalu mengambil nilainya dari [rax+68h]
  • Proses 32-bit yang berjalan di OS 64-bit memiliki TEB 32-bit dan TEB 64-bit sekaligus, dan ada konteks berguna ketika perlu mengakses keduanya, seperti kode WOW 64-bit yang berjalan di dalam proses 32-bit

Cara menentukan segment base juga berbeda menurut mode

  • Pengaturan CPU yang menentukan base address FS dan GS berbeda antara mode 32-bit dan mode 64-bit
  • Dalam mode 32-bit, nilai sebenarnya dari segment register merujuk ke segment descriptor yang didefinisikan di Global Descriptor Table dan Local Descriptor Table
  • Dalam mode 64-bit, base dikendalikan oleh dua MSR
    • FS Base, IA32_FS_BASE dalam Intel SDM
    • GS Base, IA32_GS_BASE dalam Intel SDM
  • Karena struktur ini, dalam mode 64-bit nilai register aktual FS dan GS itu sendiri tidak penting
    • Yang penting adalah prefix segment override
  • Saat men-debug proses 32-bit di WinDbg, nilai register FS dapat digunakan untuk men-dump isi “FS segment”
  • Pada proses 64-bit, cara yang sama tidak bekerja; yang bermakna adalah prefix segment override, bukan nilai segment

Pelajaran praktis bagi implementor emulator

  • Membuat emulator x86 mengharuskan penanganan detail perilaku nyata CPU seperti encoding instruksi, prefix, flag, shift count, dan segment
  • Banyak dari aturan ini hampir tidak berguna untuk penulisan kode biasa, tetapi menjadi kebutuhan langsung dalam implementasi emulator
  • Banyak hal dipelajari melalui trial and error serta mentoring, dan turut diperkenalkan juga Darek Mihocka, yang memiliki pengalaman panjang dengan emulator, beserta emulators.com
  • Jika tertarik pada optimasi x86 dan perilaku low-level, materi di situs web Agner Fog berguna

1 komentar

 
GN⁺ 2024-07-11
Komentar Hacker News
  • Sebagai tambahan, BSF/BSR juga punya keanehan. Intel SDM mengatakan jika input-nya 0, nilai tujuan tidak terdefinisi, tetapi AMD mendokumentasikan bahwa dalam kasus itu tujuan tidak diubah
    Namun glibc begitu saja memakai fakta tak terdokumentasi bahwa di Intel pun tujuan tidak diubah [1]. Karena ini, saya butuh cukup lama untuk menemukan sumber masalah di binary translator saya
    Selain itu, TZCNT/LZCNT adalah encoding BSF/BSR dengan prefiks F3, tetapi pada prosesor lama yang tidak mendukung ekstensi ini, prefiks tersebut diam-diam diabaikan. Jadi kode yang sama berperilaku berbeda tergantung CPU, tetapi setidaknya hal ini terdokumentasi
    Dalam encoding, orang sering mengeluhkan prefiks, tetapi secara pribadi menurut saya itu bukan yang terburuk. Itu sudah dikenal luas dan sampai tingkat tertentu juga terdokumentasi. Ada keanehan yang lebih buruk. Misalnya bit ekstensi REX/VEX/EVEX.RXB diabaikan jika tidak berlaku pada targetnya, tetapi pada register mask k0-k7 akan memicu #UD. Namun jika register dienkode di ModRM.rm, bit ekstensi itu kembali diabaikan
    APX menaikkan level keanehan satu tingkat lagi. Prefiks REX2 bisa meng-encode register serbaguna r16-r31, tetapi tidak bisa xmm16-xmm31, dan prefiks EVEX memiliki beberapa layout tergantung opcode; bit ekstensi yang dipakai untuk register juga berbeda tergantung jenis registernya. Register XMM memakai X3:B3:rm dan V4:X3:idx, sedangkan register serbaguna memakai B4:B3:rm dan X4:X3:idx. Sudah 1 tahun berlalu dan saya masih belum menyelesaikan decoder APX, jadi saya tidak bisa memberikan daftar lengkapnya
    [1]: https://sourceware.org/bugzilla/show_bug.cgi?id=31748

    • Selama setahun terakhir, di sela-sela waktu saya sedang menulis ulang decoder x86 QEMU. Awalnya ini pekerjaan yang diperlukan untuk menambahkan dukungan AVX, tetapi sekarang hanya tersisa beberapa opcode untuk ditulis ulang, dan setelah itu dukungan APX sepertinya juga tidak akan terlalu sulit
      Untuk EVEX, saya berencana mempertahankan bit mentah sampai opcode dibaca dan kelas EVEX diidentifikasi. Artinya, rencananya bit itu dipertahankan sampai sebelum immediate, mungkin sampai tahap sebelum ModRM
      Decoder saya sebagian besar berbasis tabel di manual, dan kodenya umumnya cukup baik. Indentasinya tidak berlebihan dan sebagian besar tahap dipisahkan atau mudah dikenali. Karena output-nya adalah kode JIT, tidak perlu sangat efisien, dan lebih baik mudah dibaca. Ini juga bukan tempat sebagian besar waktu dihabiskan
      Meski begitu, ada beberapa kasus ketika manual salah atau tidak menceritakan semuanya. Tabelnya juga sudah bertahun-tahun tidak diperbarui, misalnya tidak ada instruksi register K. Ke depannya sepertinya akan ada lebih banyak pekerjaan manual
      Situasinya sedikit dijelaskan di komentar paling atas: https://github.com/qemu/qemu/blob/59084feb256c617063e0dbe7e6...
      Seperti disebutkan di atas, masih ada beberapa instruksi yang ditangani kode lama, terutama BT/BTS/BTR/BTC. Kodenya sudah ditulis, tetapi belum digabungkan
    • Ada keanehan lain dari era 486 dan Pentium. BSWAP EAX mengonversi antara little-endian dan big-endian, dan sejak awal merupakan instruksi 32-bit
      Namun ada prefiks 0x66 yang mengalihkan mode 16-bit dan 32-bit. Jika ini diterapkan pada BSWAP EAX, terjadi hal aneh yang tidak terdefinisi
      Pada beberapa arsitektur CPU, seperti perbedaan Intel dan AMD, prefiksnya sekadar diabaikan; pada yang lain terjadi perilaku yang saya sebut “swap internal”. Misalnya, dari empat byte yang tersimpan di EAX, byte nomor 1 dan 2 saling bertukar
      0x11223344 menjadi 0x11332244
    • Membayangkan semua logika ini harus dibuat bekerja dengan tepat di silicon, apalagi dengan cepat, membuat kepala pening
      Dulu x86 adalah moat Intel, tetapi sekarang tampak seperti beban mimpi buruk yang harus terus dipikul
    • Kombinasi semantik dan encoding LZCNT terasa seperti gol bunuh diri. Ia dienkode sebagai instruksi BSR dengan prefiks yang diabaikan pada legacy, dan untuk input non-0 nilai baliknya adalah ukuran operand dikurangi nilai balik versi legacy
      Memang ada fungsi bernama clz(), tetapi jika LZCNT hanyalah BSR dengan semantik input 0 yang berbeda, biaya menambahkan pengurangan ekstra dalam implementasi demi kompatibilitas rasanya kecil saja
    • Saya tidak terlalu paham bidang ini, tetapi sepertinya menarik jika antarmuka JTAG dihubungkan ke CPU x86 lalu menjalankan instruksi satu per satu sambil mencatat semua nilai register
      Jika program yang sama juga dijalankan pada CPU yang diemulasikan dan statusnya diverifikasi sama pada setiap instruksi, kita bisa menguji apakah emulator benar-benar meniru hardware secara sempurna
  • Orang yang keren. Menulis assembly terasa sederhana, dan saya juga suka estetika susunan vertikalnya
    Pengalaman saya yang sedikit mendekati pekerjaan seperti OP hanyalah ketika mencoba membuat teman yang menulis JS memahami stack, lalu bersama-sama membuat VM mini dengan ISA kecil: https://gist.github.com/darighost/2d880fe27510e0c90f75680bfe...
    Kami bisa saja menggali lebih dalam dan saya berharap kami melakukannya, tetapi mungkin itu akan menyimpang dari tujuan pendidikan semula. Saya harus menghubungi teman itu untuk menanyakan apakah ia masih ingin belajar bersama. Ia menghasilkan banyak uang dari pengembangan web yang keren sehingga tidak punya waktu untuk mendalami, sedangkan saya pengangguran sehingga punya waktu dan energi yang nyaris seperti lautan tak terbatas, jadi tidak mudah

    • Mungkin itu bisa menjadi kesempatan untuk belajar JS dari teman tersebut
    • Sebagai orang non-CS, kode ini menjadi pintu masuk yang bagus untuk mulai memahami cara kerja internal, dan berkat itu berlanjut menjadi perjalanan assembly yang cukup keren dan sulit. Saya berniat menggali lebih dalam
  • Justine Tunney dan emulatornya juga layak dilihat. https://justine.lol/blinkenlights/
    Dokumentasinya memberi tur yang sangat bagus tentang bagaimana CPU bekerja

    • Menakjubkan. Selalu membuat saya kagum
    • Nama Tunney mengingatkan saya pada sekitar 2014, ketika ia hidup tanpa rumah, berpindah-pindah, dan menulis omong kosong Twitter terkait Occupy
  • Diskusi sebelumnya ada di sini: https://news.ycombinator.com/item?id=34636699
    Sulit dipercaya sudah 16 bulan berlalu. Waktu benar-benar cepat

  • Saya sangat tidak setuju dengan pendapat bahwa “menulis emulator CPU adalah cara terbaik untuk benar-benar memahami cara kerja CPU”
    Cara terbaik adalah membuat CPU pada level gate, seperti yang dilakukan di kelas ilmu komputer yang bagus. Proyek membuat ARM yang diperkecil dari nol dulu benar-benar menyenangkan

    • Menurut saya keduanya berguna. Hanya saja merancang CPU modern pada level gate tidak terjangkau bagi kebanyakan orang, dan ada kesenjangan besar antara CPU yang dirancang di universitas dan CPU yang menjalankan kode nyata
      Membuat emulator CPU modern adalah tantangan yang lebih mudah dijangkau, dan meski hanya sebagian yang berfungsi, efek edukatifnya tetap besar
    • Setuju. Bahkan prosesor dasar dengan microcode, pipeline, superscalar, branch prediction, cache data/instruksi L1, dan controller cache L2 write-back juga tidak mudah
      Kebanyakan software engineer punya pemahaman yang tidak lengkap tentang data hazard, cache invalidation, dan pipeline stall
    • Saya rasa keduanya benar. Merangkai chip 74xx sangat memuaskan, dan membuat kita secara intuitif memahami sisi kelistrikan serta kompromi internalnya
      Namun begitu mulai membuat CPU seperti itu untuk pekerjaan bermakna yang ingin dipakai, detail-detail itu mulai terasa kurang menarik. Pada titik itu, kompleksitas perilaku dan spesifikasi menjadi lebih menarik, dan pendekatan emulator lebih mudah ditangani serta bisa mencakup lebih banyak jenis perilaku
    • Saya sedang mengikuti Nand2Tetris dan membangunnya dari level gate; baru saja menyelesaikan bab emulator VM, dan itu memakan waktu sangat lama. Sekarang lanjut ke tahap kompilasi
    • Sebaliknya, apakah Anda benar-benar akan mengimplementasikan segmentasi memori sampai ke CPU level gate? Menurut saya, dua tahap—membuat CPU yang benar-benar bekerja, lalu mengemulasikan CPU nyata termasuk cacat-cacatnya—sama-sama diperlukan untuk pemahaman yang sesungguhnya
  • Saya sudah menulis emulator cepat untuk sekitar selusin arsitektur yang bukan mainan, dan juga beberapa penerjemah JIT, tetapi x86 masih membuat saya PTSD. Saya belum pernah melihat arsitektur sekotor ini. Memang ada sejarah dan alasannya, tetapi tetap saja parah

    • Mempelajari arsitektur x86 terasa seperti mempelajari bahasa yang penuh ketidakteraturan, organ sisa, dan sistem sintaks yang saling bersaing, misalnya bahasa Prancis. Arsitektur lain seperti RISC-V atau ARMv8 jauh lebih konsisten
    • Kalau “belum pernah melihat arsitektur yang lebih kotor dari ini”, ada Itanium. Setiap kali membuka manualnya, tanpa sengaja mencarinya pun selalu menemukan hal baru yang membuat bertanya, “sebenarnya mereka berpikir apa?”
    • Saya paham. Mungkin seharusnya saya mulai dari itu sejak awal
  • Baru-baru ini, sebagai proyek sampingan, saya mengimplementasikan sebagian besar decoder x86-64 [1], dan cukup terkejut bahwa sekarang semuanya menjadi lebih rumit. Untuk tujuan saya, Sandpile.org [2] sangat berguna
    [1] Tepatnya versi x86-64 dari disfilter milik Fabian Giesen yang saya buat untuk proyek sampingan lain yang belum dipublikasikan: https://gist.github.com/lifthrasiir/df47509caac2f065032ef72e...
    [2] https://sandpile.org/

  • Disassembler 68k yang saya buat di universitas terasa seperti momen ketika Neo berkata, “Saya tahu kungfu.” Itu adalah mata rantai yang hilang yang membuat saya bisa bernalar tentang kode dari bahasa tingkat tinggi sampai transistor, lalu kembali lagi
    Kalau menulis emulator penuh, rasanya efeknya bisa satu orde magnitudo lebih besar. Tulisan yang bagus

    • Menulis emulator ISA menurut saya sebenarnya tidak terlalu membantu untuk memahami cara kerja CPU superscalar modern. Sebab hampir semuanya adalah optimisasi yang tersembunyi di dalam
  • Sepertinya ingatan saya salah. Saya ingat varian salsa20 dan kode mesin awalnya ada di cryp.to, tetapi situs Dan Bernstein adalah https://cr.yp.to/
    Saat dulu di startup kami meninjau enkripsi data tersimpan, enkripsi streaming, dan sebagainya, halaman Dan memiliki banyak implementasi berdasarkan chipset dan instruction set target. Itu di-cross-compile dari representasi assembler miliknya
    Menarik menggunakan VM dan melihat instruction set apa saja yang didukung pada awal hingga pertengahan 2000-an. Saat pengujian, kadang muncul masalah karena VM mengklaim mendukung sesuatu, tetapi implementasinya belum sepenuhnya mendukung

    • Maksudmu situs Dan Berstain… sebentar
  • Menarik melihat banyak pembicaraan di sini tentang betapa menyakitkannya assembly x86 dibandingkan RISC. Saya mengalami masalah yang justru sebaliknya ketika memisahkan kembali kode menjadi object file
    Untuk kegunaan ini, analisis x86 benar-benar mudah, sedangkan MIPS seperti mimpi buruk. Ini terutama karena yang saya pedulikan adalah referensi ke kode dan data. x86 punya konstanta immediate seukuran pointer, sedangkan MIPS punya pasangan relokasi HI16/LO16 yang berkelindan dengan graf penggunaan register, alur kode, dan instruksi branch delay, sehingga menimbulkan berbagai macam masalah
    Bukan berarti saya sedang memuji x86

    • Benar. x86 memang aneh, tetapi instruksi panjang variabel, setelah diurai ke bentuk teks, sebenarnya enak dilihat dan mudah dipahami. Masalahnya adalah instruksi lain bisa disembunyikan di tengah instruksi, sehingga tidak aman
      Hal terbesar yang dipelajari saat membandingkan assembly x86 dan C adalah bahwa signed/unsigned bukan lagi tipe, melainkan properti dari operasi
      Akan bagus kalau bisa memanfaatkan flag, dan di beberapa arsitektur seperti PPC atau armv7 itu lebih mudah, tetapi di x86 flag terlalu mudah ditimpa sehingga sangat sulit memanfaatkan nilainya