Phase 13: Tools & Protocols

Doğal Kodu Üstüne: Python, JVM, .NET ve WebAssembly Bytecode

Bir bytecode dosyası bir program ve bu dersi, her konteyner hangi notları sakladığını gösteren okuyucuları oluşturur.

Type: Build

Languages: Python, TypeScript, Java, C#, WebAssembly text

Prerequisites: Phase 13 · 32 (ELF from scratch), 33 (disassembly and control flow)

Time: ~150 minutes

Öğrenme Hedefleri

  • 16 bayt oku .pycPEP 552 başlığı ve kod nesnesini olmadan açmamarshalModül, dört tercüman versiyonu tarafından yazılmış dosyalar için.
  • JVM sınıf dosyasını JVMS Bölümü 4'ten analiz edin: sabit havuz, alanlar, yöntemler, CodeBu özellik ve javac -g- Ekle.
  • JVM kodlarını Ledger.classve listeye alınmış olduğunu kanıtlamak için javap -cAynı kaynağın dört binasındadır.
  • WebAssembly modül bölüm bölüm okuyun, her fonksiyon vücudu çözün ve sonuçları karşılaştırın wasm-objdump -d- Evet .
  • .NET birimi ECMA-335 metadatalarını PE veri dizininden MethodDefİLSpy'nin kullandığı okuyucu kütüphanesine karşı kontrol edilen her yöntemin satırları ve CIL'si.
  • Beş konteynerde bir fonksiyon için, her biçim hangi isimleri, türleri ve kontrol yapısını saklıyor ve hangilerini kaybediyor.

Sorun

Sen inşa ettin .ledger.pyve buldum .ledger.cpython-314.pycİçeride__pycache__Bir ajanı dosyanın içeriğini sordun.Unsupported opcodeve bir satır da şöyle diyordu:# WARNING: Decompyle incompleteAjan dosyanın gizlendiğine karar verdi.

Dosya gizlenmedi. İlk iki baytı2b 0eBu sayı CPython 3.14 yazıyor ve dekompiler'in opkod tabloları 3.13. araç günceldi. ajan başlığı okumadı, bu yüzden farkı göremiyordu.

Ders 33 native kodun ne kaybettiğini gösterdi: parametre isimleri, türleri ve switchBu bir atlama masası haline geldi. Bytecode konteynerleri daha az kaybeder ve her biri farklı bir şey kaybeder. Bir pyc, her değişkenin adını ve hiç bir türü saklar. Bir sınıf dosyası sadece sorulduğunda tipleri ve yerel isimleri saklar. Bir Wasm modülü kontrol yapısını korur ve özel bir bölümde taşıyılmadıkça isimleri düşürür. Bir .NET birimi bir tabloda parametreler isimlerini tutar ve yerel isimleri ayrı bir dosyayaya bırakır. Bir depolamacının hangi notlarla başladığını bilmeden, bunların herhangi birinde çıkışını yargılayamazsın.

Bu ders Python standart kütüphanesinde dört okuyucu oluşturur. Her biri konteyneri ilk bayttan okuyor, bir işlevi listeler ve oluşturduğunuz armatürlerde biçimin kendi aracı ile kontrol edilir. Son bölümde yerleştirir grade-ledgerBeş konteynerde de yan yana.

PYC Başlığı Dört Kelimedir

PEP 552 başlığı dört 32 bit kelime olarak tanımlar. Birincisi sihirli sayı. İkincisi bir bit alanı. Bit 0 seti dosyanın hash tabanlı olduğunu gösterir. Bit 1 seti check_sourceBit 0 açık olduğunda, üç ve dört kelime kaynak mtime ve kaynak boyutu. Bit 0 seti ile, kaynakın 64 bit SipHash'ini tutar.

Sihirli sayı orada yaşıyor .Include/internal/pycore_magic_number.h3.11'den bu yana kuralı Python 3.n'un 2900 + 50n'de başlaması ve bir yayın döngüsü içinde her bytecode değişimi bir tane eklemesi.\r\nPython 3.15.0 2026-10-09'da yayınlandı ve bu cihazları yapan makineye yüklenmediğinden, bu yüzden numarası başlık dosyasından değil, buradaki bir dosyadan geliyor.

Anlatıcı başına iki pyc dosyası var.compile_pyc.pyOnları yazdı .py_compile.compile(..., dfile="ledger.py")Böylece kaydedilen dosya adı ledger.py3.14 dosyalarının ilk 16 baytı:

output
checked-hash   2b 0e 0d 0a  03 00 00 00  c7 16 c7 fc c5 1d 81 01
timestamp      2b 0e 0d 0a  00 00 00 00  8d 60 cf 6a  f1 01 00 00

Zaman damgası başlığı kelimesi kelimesi oku. 03 00 00 00veya 00 00 00 00Bayrak kelimesi.8d 60 cf 6a0x6acf608d = 1791975565, mtime of ledger.pyDosya oluşturulduğunda.f1 01 00 00Kaynak boyutu 497. Kontrol edilen hash dosyası 3 ve sekiz hash bayt yerine bayraklar taşıyor.

InterpreterMagic bytesWordFile bytesgrade.co_code bytesgrade instructions
3.11.14a7 0d 0d 0a349510548031
3.12.12cb 0d 0d 0a35319376226
3.13.12f3 0d 0d 0a35719437026
3.14.62b 0e 0d 0a36279659635

read_pyc_header()İçeridecode/main.pyBu alanları geri gönderir ve kelimeden versiyonu isimlendirir. importlib.util.MAGIC_NUMBERÇalışan tercüman için aynı dört byte verir. python3 code/main.py comparekaynağı her bir ile birleştirir python3.11- ...python3.15PATHHer biri için, okuyucu tarafından görülen başlığı, tercümanın kendisi hakkında bildirdiği gerçeklerin yanında basıyor.

Marshal Kod Nesne Alanı Alanı Yazıyor

Her bir cihazın 16 byte'si e3- Evet .0x80 | ord('c'): ile kodlanmış bir nesne için marşal tip koduFLAG_REFSatır, okuyucuya nesneyi daha sonra hatırlamasını söyler.rReferanslar. Marshall belgeleri biçimin amaçlı belgelenmemiş olduğunu ve kod nesnelerin sürümler arasında uyumlu olmadığını söylüyor. Her iki ifade de doğru ve biçim hala okunmak için yeterince küçük. Python/marshal.cTip kodlarını listeler: NHiç değil.i32 bit int, sbytes, zve ZKısa ASCII ipleri, (ve )Tüpler,cbir kod nesnesi, rBağlantılar, referans alanını çocuklarının önünde rezervasyon eder, böylece okuyucu da aynı şeyi yapar.

w_complex_object()3.11 ile 3.15 arasında bir kod nesnesini sabit bir sırada yazıyor.argcount- Evet .posonlyargcount- Evet .kwonlyargcount- Evet .stacksize- Evet .flags32 bit inci olarak, o zaman code- Evet .consts- Evet .names- Evet .localsplusnames- Evet .localspluskinds- Evet .filename- Evet .name- Evet .qualnameO zaman nesneler olarak firstlinenoO zaman ...linetableve exceptiontable3.14 dosyasının modül kod nesnesini 17 byte'den elle çöz:

output
00 00 00 00   argcount 0
00 00 00 00   posonlyargcount 0
00 00 00 00   kwonlyargcount 0
01 00 00 00   stacksize 1
00 00 00 00   flags 0
f3 22 00 00 00   type 's' with FLAG_REF, 34 bytes of co_code follow
80 00  52 06  74 00  52 01  17 00  74 01 ...

Kod baytları her talimat için iki: 80 00- Evet .RESUME 0- Evet .52 06- Evet .LOAD_CONST 6- Evet .74 00- Evet .STORE_NAME 0ve17 00- Evet .MAKE_FUNCTION. 3.14 listesi, aynı beş modül düzeyinde açıklamalar basıyor.codeDört kutuş kod nesnesini tutan sabitler gelir ve sonralar.names- Evet .('LABELS', 'percent_change', 'grade', 'total', 'grade_label')- Evet .

Unmarshallerİçeridecode/main.pyBu düzeni ve 3.9 ve 3.10'un kullandığı eski düzeni okuyor.nlocals- Evet .varnames- Evet .freevarsvecellvarsBu, bir alan oluşturur.CodeObjectGerçek nesneyle aynı özellik isimleri ile.varnamesFiltrelerlocalsplusnames- ...CO_FAST_LOCALBirazcık .localspluskinds- Evet .gradeDört dosyanın her birinde geri gönderir.('score', 'bucket')- Evet .firstlineno10 ve boş olmayan bir tane.linetable- Test .test_unmarshaller_agrees_with_the_running_interpreterher alanı marshal.loads()Diğer üç dosya bizim kodumuzla okunur ve okunmaz.marshalBu tercümanın altında.

Dört Anlatıcı, dört Bytecode derecesi

Bu belgede her versiyonda bir uyarı vardır: bytecode CPython'un uygulanma ayrıntılarıdır ve aynı kalmayacağı garanti edilmez.python3.1N -m dis ledger.pyHer tercüman için yakalamıştı.

3.11'degradeBu şekilde başlar:

output
 11           2 LOAD_FAST                0 (score)
              4 LOAD_CONST               1 (10)
              6 BINARY_OP                2 (//)
             10 STORE_FAST               1 (bucket)
 12          12 LOAD_FAST                1 (bucket)
             14 LOAD_CONST               2 (9)
             16 COMPARE_OP               5 (>=)
             22 POP_JUMP_FORWARD_IF_FALSE     2 (to 28)

BINARY_OPOfset 6'da ve sonraki talimat 10'da oturuyor. Arasındaki iki bayt, PEP 659'dan gelen özel adapttif tercümanın bir içe aktarım girişidir. COMPARE_OP16'da 22'den sonra, yani iki önbelleğe sahip.show_caches=True3.12 yerineLOAD_CONSTEk olarak .RETURN_VALUE- Evet .RETURN_CONST, atlamayı POP_JUMP_IF_FALSE, ve delirtiyor .grade80'den 62 byte'ye kadar. 3.13 etiket basıyor.L1- ...L4Karşılaştırmak yerine CLI verir -Cdepolama ve-O3.14 çıkarır RETURN_CONSTtekrar ekliyor LOAD_SMALL_INTve LOAD_FAST_BORROW, ve eklemler NOT_TAKENHer şartlı sıçrayıştan sonra:

output
 12         20       LOAD_FAST_BORROW         1 (bucket)
            22       LOAD_SMALL_INT           9
            24       COMPARE_OP             188 (bool(>=))
            26       CACHE                    0 (counter: 0)
            28       POP_JUMP_IF_FALSE        3 (to L1)
            30       CACHE                    0 (counter: 0)
            32       NOT_TAKEN
  • Evet .-C -Olistede gösteriler 13 CACHEİçeride yazılar grade: beş gün sonra BINARY_OP, birbiri ardınca .COMPARE_OP, birbiri ardınca .POP_JUMP_IF_FALSEAdaptif cihaz, kasların ne için olduğunu gösteriyor.dis.dis(grade, adaptive=True)Parmak iziRESUME_CHECKve COMPARE_OP_INTAçık bir listeye göreRESUMEve COMPARE_OPvetotalgösterilerFOR_ITER_LIST- Evet .BINARY_OP_ADD_INTveJUMP_BACKWARD_NO_JITUzman biçimler aynı yerde yaşar.co_codeBir pyc asla bunları içermez, çünkü uzmanlaşım hafıza ile gerçekleşir.

Bu nedenle dekompiler versiyon tavanlarına sahiptir. uncompyle6 3.9.3 Python 1.0'dan 3.8'e kadar bytecode kabul ettiğini belirtir. decompyle3 3.9.3 3.7 ve 3.8'i okuyor. xdis 6.3.0, her ikisinin arkasındaki çapraz versiyon sökücü, 3.13.python_3_13.cppBu dosyada ve bilinmeyen bir opkod yazısında.Unsupported opcodePyLingual 3.6'dan bu yana yayınlanan her sürümü iddia eder ve kendi çıktısını yeniden oluşturmak ve kontrol etmek için 3.8'den 3.15'e kadar yorumcu kullanır. 3.14 dosyası için ilk üç dosya reddedilir veya başarısız olur ve sadece son iki geçerlidir. Bir araç seçmeden önce sihirli kelimeyi okuyun.

Sınıf Dosyası Kendi Sözlüğünü Korur

JVMS Java SE 25, bölüm 4.1, düzenlemeyi bir yapı olarak verir: magic- Evet .minor_version- Evet .major_version- Evet .constant_pool_count, sabit havuz,access_flags- Evet .this_class- Evet .super_class- ...birbirliği, alanlar, yöntemler, özellikler.0xCAFEBABEJava SE 25 sanal makinesi büyük sürümleri 45 ila 69 arasında kabul eder ve 65535'in küçük bir kısmı bir ön görüntüleme özelliği dosyasını işaret eder.Ledger-g.class- Evet .ca fe ba be 00 00 00 41 00 5e: büyük 65 Java 21 ve 0x5e= 94 sabit havuz sayısıdır.Ledger-jdk25.class, aynı kaynaktan javac 25.0.2 tarafından oluşturulmuş, başlıyor ca fe ba be 00 00 00 4569 büyük.

Sürekli havuz sınıf dosyasının kendi sözlüğünü tutduğu yerdir.#13- ... ...-gBu bir MethodrefBu sınıfı işaret ediyor.#8ve isim ve tip #14. Indeksleri takip ederek Ledger.grade:(I)ITam olarak ne demek oluyor ?javap -v//- Deskriptör .(I)IBir tane .intparametre ve bir intSonuç.([I)I- Evet .totalParametre bir intArray. kodun dokunduğu her yöntem, alan ve sınıf böyle bir giriş vardır ve bölüm 4.10'daki doğrulayıcı talimatları onlarla aynı olmayan bir dosyayı reddeder.ClassFilesınıfı code/main.pyTablo 4.4 ' deki 17 sabit etiketleri okuyor ve bir Longveya DoubleBilinmeyen bir etiket ve son özelliğin ardından baytlar üzerine yükseltilmektedir.

Atributlar sınıf, her alan, her yöntem ve CodeJVMS, sanal makineyi bilmediği özellikleri görmezden gelmesi gerektiğini söylüyor, bu nedenle hatalama tabloları bir yöntem vücudu değiştirmeden eklenebilir veya düşebilir.-gYerel değişkenler dahil tüm debugging bilgileri oluşturur, varsayılan satır numaraları ve kaynak dosyası sadece oluşturur ve -g:noneÜçü de ölçüyü oluşturur:

BuildBytesConstant pool countLineNumberTableLocalVariableTableSourceFilegrade code bytes
javac -g1832947 methods6 methodsyes50
javac1581827 methodsnoneyes50
javac -g:none140479nonenoneno50

50 byte kodunu gradeÜç dosyada da aynıdır ve test onu kontrol ediyor.-geklenir gradeBir tane .LocalVariableTableSatır: başlangıç 0, uzunluk 50, boşluk 0, isim score, tanımlayıcı IBu olmadan bir dekompiler 0 tipi boşluğu görür .intBir isim uydurmak zorunda.StackMapTableHer yapıtaşta kalır çünkü doğrulayıcıya ihtiyacı var ve BootstrapMethodsBu özellik kalır çünkü main Bağlantıları bağlarinvokedynamic- Evet .

Masa Çıkartıcıları Elle Çıkart

JVMS 6. bölümünde her talimat bir opcode byte ve opcode isimlerini veren operandlar olarak tanımlanır. Şube ofsetleri, şube opcode adresine göre 16 bit imzalanmış değerlerdir. Bu, x86-64'ten farklıdır.tableswitchve lookupswitch32 bitli operandları 4'ün katı bir adresden başlayacak şekilde sıfırdan üç byteye kadar doldurma ile başlayın.

bytesgrade4'te bir bilgisayarLedger-g.class- ...

output
aa                 tableswitch at pc 4
00 00 00           padding to pc 8
00 00 00 2c        default: 4 + 44 = 48
00 00 00 06        low 6
00 00 00 0a        high 10
00 00 00 2a        case 6:  4 + 42 = 46
00 00 00 28        case 7:  4 + 40 = 44
00 00 00 26        case 8:  4 + 38 = 42
00 00 00 24        case 9:  4 + 36 = 40
00 00 00 24        case 10: 4 + 36 = 40

36 byte, tek talimat ve tümü.switch (score / 10)9 ve 10 için ortak bir dava ile.switch33 dersinde sınır kontrolü yapılması gerekiyordu.lea, a movslq, bir addBurada hedefler talimatlarda ve aynı zamanda birlikte olmaları da var.

disassemble_jvm()Tüm 202 tanımlanmış opkodları operand genişlikleri ile kapsar, dolgu kuralını izler ve operandları yol gösterir javap- Evet.compare_with_javap()Tutuklananları inceliyor .javap -c -pÇok satırlı metin de dahil olmak üzere tableswitchBu test, tüm dört yapıdaki 108 talimatın 108'ine uyum sağlıyor.test_branch_offsets_are_relative_to_the_branch_instructionkontrollerifne6'da pc 1 inişinde.if_icmpge33'te 13'te iniş yaparak vegoto30'da 10'da geriye inmek.

Radare2 6.2.4, bu dersler için kaynaktan inşa edilmiş, aynı dosyayı okuyor. rabin2 -IRaporlararch java- Evet .lang java 21- Evet .linenum truevelsyms true, yani versiyon ve iki düzeltme tablosunun okunması. pd 14 @ sym.Ledger.gradebasıyor tableswitchBeş hedefine oklar ile vurur.Unable to parse code attribute: 2Bu dosyada, araçın kendi adları sınırlı ve bir neden olarak kalmak için javap- İbadetçi olarak. Bu biçim için dekompilicular Python ile karşılaştırıldığında olgunlaşmıştır. CFR 0.152 MIT lisanslı. Procyon 0.6.0 ve Vineflower 1.12.0 Apache-2.0'dur ve Vineflower Java 21 desteği ile Fernflower bir çatal. Fernflower'in kendisi IntelliJ Topluluğu depolarının içinde yaşıyor. JADX 1.5.6 Android DEX biçimini kapsar ve JD-GUI son sürümünü 2019'da yayınladı. Harrand, Soto-Valero, Monperrus ve Baudry 2020 yılında sekiz tane ölçtüler. Sintiktiksel olarak en iyi üretilen çıkış sınıfların yüzde 84'i için ve semantik olarak eşdeğer çıkış yüzde 78'dir. Meta-decompilerleri, tek bir araç tarafından işlenmeyen sınıfların yüzde 37,6'sını geri aldı. Tipler, isimler ve doğrulanmış yığınlar biçimi çözülebilir hale getirir. Hiçbir dekompiler tamamlanamaz.

Bir Wasm Modül Bölümler Listesidir

Temel özellikler bir modülün önbellek olarak tanımlanmaktadır.00 61 73 6d 01 00 00 00, iplik \0asmve 1. versiyon, ardından bölümler.u32LEB128'deki boyut ve içeriği. 1 ila 13'e kadar olan tanımlı bölümler, her biri en fazla bir kez ve sırada. 0 isimli özel bir bölümdür, herhangi bir yerde izin verilir. Tüm tam sayıların LEB128 olduğu içine4 00100 ve 80 081024'e kadar.wasm-objdump -h- Evet .ledger.wasm7 bölümden bir liste oluşturur ve WasmModuleAynı yedi tane aynı yük oranları ile bulur:

SectionPayloadCountWhat it holds for ledger
Type10 to 263(i32) -> nil, (i32, i32) -> i32, (i32) -> i32
Import28 to 391env.log with type 0
Function41 to 464type index for each defined function
Memory48 to 511one page, no maximum
Export53 to 1105memory and the four function names
Code113 to 2714bodies of 23, 63, 44, and 23 bytes
Data273 to 2901F\0D\0C\0B\0A\0 at offset 1024

Fonksiyon ve Kod bölümleri arasındaki bölme var, böylece bir tarayıcı tüm dosya gelmeden önce oluşturmaya başlayabilir. Her kod girişinin bir boyutu, yerel açıklamaların vektörü ve bir ifadedir.gradeYerli vektör 01 7f: bir grup bir i32- Evet .bucket, türüyle ve adı olmadan.

Kontrol akışı yapısal olarak yapılandırılır.block- Evet .loopveyaifEtiketler yuva derinliği ile indekslenir.br_tableEtiket listesi indeksi eden bir operand aracılığıyla dolaylı bir şubesi gerçekleştirir.gradeDosya ödemesi0xa3- ...

output
0e                  br_table
0b                  11 labels follow
04 04 04 04 04 04   buckets 0 to 5: label 4, the outermost block, returns 0
03 02 01 00         buckets 6, 7, 8, 9: labels 3, 2, 1, 0
00                  bucket 10: label 0, shared with 9
04                  default label 4

Her etiket derinliktir. 0 etiket en içidir.block, ve kodunun ardından end- Evet .i32.const 4Ardından br 4Sonuç blokundan çıkmış.blockMasanın önündeki talimatlar, on bir etiket ve beş etiket.brTümden sonra talimatlar switch, ve bir onaylayıcı yapıdan çıkan herhangi bir dalı reddeder.disassemble_wasm()derinliği izler. Yani listeleme indentleri gibiwasm-objdump, unreachable- ...i64.extend32_s, ve tutsaklarla eşleşir .wasm-objdump -dHer iki yapı için de 77 talimatın 77.'sinde.

Adlar, basit bir modülün düştüğü tek şeydir.wasm-objdumpHâlâ izler <grade>Çünkü Dışarı çıkarma bölümü, işlev indeks 2'e doğru bir dizileri haritası yapar ve okuyucu da aynı şeyi yapar. 290 baytlı dosyada yerel isimler hiçbir yerde bulunmaz. wat2wasm --debug-namesAdaylık bir bölüm ekliyor nameModül adı, fonksiyon adı ve yerel isimler için alt bölümler ile. Dosya 425 byte'ye kadar büyüyor. Okuyucu bulur {0: 'score', 1: 'bucket'}2 ve fonksiyon için{0: 'values', 1: 'count', 2: 'sum', 3: 'i'}3. fonksiyon için.wasm-stripBu kısım, bir kez daha kaldırılır. Araç-konvensiyon deposu iki taşıyıcıyı daha tanımlar..debug_*Gümrük bölümleri.sourceMappingURLÖzel bölüm, sütun numaraları ikili olarak byte karşılaştırılmış olan bir kaynak haritasını gösterebilir.

2026-09-15'te yayınlanan 1.0.42'de, araçlar 2026 yılının sonunda hareket etti.wasm-decompileBu makinede Homebrew'un 11 aletini bulmuştur.print- Evet .objdumpvedemangle. Binaryen versiyonu_133 wasm-dis6.2.4 modülü de okuyor.rabin2 -Idiyor kiarch wasm- Simgolu .gradeBaşlıyor .0x8e, yerel vektörün başladığı yer,wasm-objdump0x8bBu listeye eski mnemonikler kullanılıyor.get_local- Evet .set_localvebrtable 11 4 4 ...Etiket sayısını yazdırarak. Üç alette de baytlar aynıdır ve yazım farklıdır, bu yüzden bir karşılaştırma, alındığı aletin adını vermektedir. Bu ders için yapılan araştırma, Ghidra'nın işlemci dizininde WebAssembly işlemci modülü bulunmadı ve bu liste tam olarak okunmadı. Üçüncü taraf Ghidra-wasm eklentisi v2.4.0 Ghidra 12.0'yu hedef alır. Özelliklerin kendisi bir canlı belgedir. W3C sayfası wasm-core-213 Ekim 2026'da başvuru önerisi taslağı, özetinde 3.0 sürümü açıklanır ve editörlerin taslağı aynı tarihte bulunur.

ECMA-335 Metadata Programın Kendi Veritabanı

.NET birimi bir PE dosyası. LedgerNet.dll ile başlar.MZ, üç bölümden oluşuyor ..text- Evet ..rsrcve.reloc, ve makineyi ilan eder .0x14cÇünkü kompiliör platform tarafsız kodu yayımladı. Seçimsel başlıkta, CLR Runtime Header'in veri dizisi 14 RVA'ya işaret eder 0x2008. ECMA-335 II.25.3.3 72 byteyi burada tanımlar:

output
48 00 00 00   Cb 72
02 00 05 00   MajorRuntimeVersion 2, MinorRuntimeVersion 5
78 21 00 00   MetaData RVA 0x2178
84 07 00 00   MetaData size 1924
01 00 00 00   Flags: COMIMAGE_FLAGS_ILONLY
05 00 00 06   EntryPointToken 0x06000005, MethodDef row 5, which is Main

RVA' daki metadata kökü 0x2178İmza ile başlar.42 53 4a 42, şöyle okuyor:BSJB, sonra 1.1 sürüm ve dizileri v4.0.30319Beş akış başlığı takip ediyor:#~- Yapabilir miyim ?0x6cO zaman ...#Strings- Evet .#US- Evet .#GUIDve#BlobII.24.2 her tür akım en fazla bir kez meydana gelir diyor.#~Akış bir ile başlar.HeapSizesBayt, burada sıfır, yani her ip, GUID ve blob indeks 2 bayt genişliğinde ve 64 bit.ValidMaske, mevcut tabloların isimlerini belirler.

TableIdRowsRow sizeWhat the rows say
Module0x00110the module name and its GUID
TypeRef0x01216System.Object, System.Console, 19 more
TypeDef0x02414<Module>, Ledger, and two compiler helpers
Field0x0426Labels and a static array initialiser
MethodDef0x06614the five methods and .cctor
Param0x0856before, after, score, values, score
MemberRef0x0a16612 attribute constructors and the 4 methods that Main calls
CustomAttribute0x0c126attributes the compiler and the SDK add
StandAloneSig0x1132the three local variable signatures
Assembly, AssemblyRef, ClassLayout, FieldRva, NestedClass1, 2, 1, 1, 1identity, references, layout

Bir satır boyutu hiçbir yerde saklanmaz. II.22 her sütunu bir tür verir ve II.24.2.6 boyut kuralını verir.HeapSizes. Basit bir tablo endeksi, 65536 satırdan az olduğu halde iki byte. B etiket bitleri ile n tabloya işaret edebilen kodlanmış bir indeks, her bir tabloda 2^(16-b) satırdan az olduğu halde iki byte. CliAssemblyBu kurallardan 14 satır boyutlarını hesaplar ve satır sayılır. Sistem.Reflection.Metadata dump'u, aynı boyutları ve aynı tablo sıfırlamalarını rapor eder. Bu test aritmetikleri kanıtlar.

III.1.9 simgesini tanımlar: yüksek bayt tablo, düşük üç bayt ise 1 tabanlı satırdır. 0x06000002- Evet .MethodDefSatır 2. Bu satır RVA'ya sahiptir.0x2060, isimGrade, ve bir imza blob .00 01 08 08: Varsayılan arama konvansiyonu, bir parametresi, geri dönüş int32, parametreint32- Bu .ParamListParam, adı scoreParam masası , ILSpy ' nin yazdırma gücünün bir nedeni .Grade(int score)RVA'da, bir boşaltma yapısı.0x2060Metod vücudu bir yağ başlığı ile başlar:

output
13 30         flags 0x13: fat, init locals; size 3 dwords
02 00         MaxStack 2
2d 00 00 00   CodeSize 45
01 00 00 11   LocalVarSigTok 0x11000001

StandAloneSig1 satır tutulur 07 01 08- Evet .LOCAL_SIG, bir yerel, int32. Yerli bir tür ve isim yoktur. ECMA-335 bölümü V'nin hatalama değişimi biçiminde yerli isimleri tutar ve .NET SDK onları bir PDB'ye yazar. Bu cihaz DebugTypeHiçbir şey yok, yani hiçbir tablo ve dosya adı taşımaz.bucket- Evet .PercentChangeyerine küçük bir başlık var: ilk baytı 3a- Evet .0x0e << 2 | 2, 14 baytlı bir vücut ve yerel olmayan ve varsayılan 8'lik bir yığın.

45 kod baytları GradePartition III opcode tablosu ile çözülür. switch- Opcode0x45Ardından bir u32Sayım ve o kadar çok.int32Tablo sonrası talimat ile ilgili hedefler:

output
IL_0000: 02           ldarg.0
IL_0001: 1f 0a        ldc.i4.s 10
IL_0003: 5b           div
IL_0004: 0a           stloc.0
IL_0005: 06           ldloc.0
IL_0006: 1c           ldc.i4.6
IL_0007: 59           sub
IL_0008: 45 05 00 00 00 08 00 00 00 06 00 00 00 04 00 00 00 02 00 00 00 02 00 00 00
                      switch (IL_0029, IL_0027, IL_0025, IL_0023, IL_0023)
IL_0021: 2b 08        br.s IL_002b
IL_0023: 1a 2a        ldc.i4.4, ret

Yükleyici , switchBu yüzden 6 numaralı dava ilk hedef.disassemble_cil()tablolardaki simgelerden birikim: ldsfld Labels- Evet .call Grade- Evet .call System.Console::WriteLineveldstr ' '- ...#USTüm bunların orakelinin adıMetaDump, kısa bir C # programı , üzerinde inşa edilmiş armatürlerde .System.Reflection.MetadataBu, her tablo sayısını, boyutunu ve sıfırlamasını, her yöntemin RVA'sını, imzasını, parametrelerin isimlerini ve IL bytesini ve 226 alanını yazdırırır.System.Reflection.Emit.OpCodes- Evet .compare_with_metadump()246 kontrol için aynı fikirde.prefixDers masasında 219 giriş var çünkü içerir no.III.2.2'den, emisyon tablosunun hiç sahip olmadığı.

Format için araçlar: ILSpy 11.0 (MIT, 2026-08-12) Avalonia masaüstü yapılandırması aracılığıyla macOS'ta çalışır. ilspycmd11.1.0.9782 ile birlikte IL baskı-ilve bir yöntem seçerken -m 0x06000002. dnSpyEx 6.6.0 sadece Windows'ta dnSpy devam ediyor.ildasmBu yüzden, bu sistemin içinde değil, NuGet paketleri olarak gemiyi kullanıyoruz.rabin2 -Idiyor kiarch cilve class PE32veislistelerLedger.Grade(int:score):intBu yapıtaşta CIL sökücü bulunmamaktadır. Bu yapıtaş .NET SDK 8.0.205 ile inşa edilmiştir çünkü bu yapıtaş makinesinde kurulan SDK. .NET 10 2025-11-11'den beri geçerlidir ve aynı ECMA-335 yapılarını yazar.

Her konteynerde Ne Var

  • Yapacağım .gradeBir masadaki beş konteynerden.python3 code/main.py keeps, okuyucuları cihazların üzerinde kullanır:
ContainerBody bytesFunction nameParameter namesParameter and return typesLocal namesLine numbersControl flow
ELF object, clang -O0118grade in .symtabnonenonenonenone, no .debug_* sections5 jumps and 1 indirect jump through a relocation table
CPython 3.14 pyc96co_qualnamescorenonebucketco_linetable, first line 104 compare and POP_JUMP_IF_FALSE pairs
JVM class, javac -g50constant poolscore in LocalVariableTable(I)Inone beyond the parameterLineNumberTable, 6 rows1 tableswitch
WebAssembly63Export sectiononly with a name section(i32) -> i32only with a name sectionnone6 nested blocks, 1 br_table, 4 br
.NET assembly45MethodDefscore in Paramstatic int32(int32)none, types only in StandAloneSignone without a PDB1 switch, 1 br.s

Tabloda göreceğiniz dekompiler sonuçları açıklanıyor.int total(const int *, int)- ... ...int total(long param_1, uint param_2)Bu nedenle Python dekompilerleri her versiyonda kırılır ve isimleri ücretsiz olarak kurtarır. Bir sınıf dosyası tipleri ve doğrulanmış yığınları tutar, isimleri tutar.-gBu, bir "sürekli"switchBir Wasm modülü yapıyı inşa ederek korur.wasm2wat.NET birimi en tam tanımını korur, çünkü metadata tabloları normatifdir ve hala yerel bir ad için bir PDB gerektirir.

Tablo, dekompilerin neyle çalışması gerektiğini ve hangi çıkışlarının kaydedilen bir gerçeği kopyaladığını ve hangi çıkışların birini çıkarıldığını söyler. ILSpy çıkışında bir parametrel adı bir kopyadır. Aynı çıkışta bir yerel isim bir PDB bulunmadıkça bir sonuçtır. Ders 35 bu farkı her iddianın otoritesi olarak kaydeder.

Yapın

Demo çalıştır:

bash
cd phases/13-tools-and-protocols/43-bytecode-and-managed-formats
python3 code/main.py
npx tsx code/main.ts

Çıktı bir JSON raporu. her pyc için hem başlıkları, dosya ve gradeBu sayede, sınıfın oluşturduğu tüm sınıflar için, sınıfın sürümü, boyutları, hatalama tablosunun sayıları ve javapHer Wasm yapı için bölümler, türler, fonksiyonlar, isim bölüm içeriği ve wasm-objdumpbirleştirme için CLI başlığı, akışları, masa satır sayıları, yöntemleri ve Sistem.Reflection.Metadata anlaşması verir.PATH- Evet .

Oku code/main.pyBu sırada:

  1. ReaderKürsör ve tam sayılar genişliğini tutar. uleb()ve sleb()Wasm ve .NET blob toplularının ihtiyaç duyduğu LEB128 okuyucuları.
  2. read_pyc_header()- Evet .UnmarshallervePycFileBir pyc oku.parse_dis_text()Tutuklananları okuyor .dislisteler, böylece çapraz versiyon karşılaştırması test sırasında dört tercüman gerekmez.
  3. ClassFilesınıf dosyasını okuyor ve disassemble_jvm()Kodunu çözüyor.parse_javap_code()ve compare_with_javap()- Orakel kontrolü.
  4. WasmModulebölümleri okuyor ve disassemble_wasm()Bir ifadeyi derinliğiyle çözüyor.compare_with_wasm_objdump()- Bu oracle kontrolü.
  5. CliAssemblyPE, CLI başlığı, metadata kökü, akışlar ve tabloları yürütür. TABLE_SCHEMASve CODED_INDEXESII.22 ve II.24.2.6 veriler olarak kullanılır. disassemble_cil()Bir cesedi çözüyor ve compare_with_metadump()- Kontrol et.
  6. NativeObjectişlevi sembollerini listedecek kadar ELF okuyor ve ders 33 listesi metni olarak alıyor. what_each_keeps()ve figure_data()karşılaştırmayı yapın.

Beş emir daha var.python3 code/main.py pyc FILE- Evet .class FILE- Evet .wasm FILEvedll FILEVerdiğiniz dosyada yapı ve her fonksiyonun listesi basın. python3 code/main.py comparebir araya getirir ledger.pyHer tercümanla birlikte.PATHVe her birinin yazdıklarını basıyor.

Kullan

Bu sırada okuyucuları kendi yaptığın bir konteynerde kullan.

  1. İlk baytları oku ve biçimi isimlendirin.0d 0abir pyc,ca fe ba besınıf dosyası, 00 61 73 6dbir modül, 4d 5aVeri dizin 14'te bir monte ayarlanır.
  2. Başlığı okuyun ve adı verilen üretici versiyonunu yazın. Bir pyc için sihirli kelime, bir sınıf dosyası için ana versiyon, bir montaj için çalıştırma süresi ve metadata versiyonu zincirleri.
  3. Çalıştırmadan önce çalıştırmayı planladığınız her dekompilerle o versiyonla karşılaştırın.
  4. Okuyucuyu ve biçimin kendi araçlarını aynı dosyada çalıştırın ve onları satır satır karşılaştırın.
  5. Kayıp veya kayıp olarak belirleyin ve tablo, özelliği veya bölümün adını verin.
  6. Sadece sonra dekompiler çıkışını okuyun. Her bir ismi işaretleyin ve bir gerçekin bir kopyası veya bir sonucu olarak yazın.

Bir tercüman, JDK, SDK veya wabt sürümü değiştirdiğinde, ile yeniden inşa edin.build_fixtures.shVe yeniden yakalamak, çünkü değiştirilmiş bir listede değişmiş bir yer vardır.

İnteraktif Laboratuvar

Laboratuvar A: başlığı okuyun, sonra kod nesnesini okuyun

  • Evet .codeDizin, açık Python:
bash
cd phases/13-tools-and-protocols/43-bytecode-and-managed-formats/code
python3 -q

Çık:

python
from main import *
pyc = load_pyc("ledger.cpython-311.pyc")
pyc.header.to_dict()
pyc.module.names
pyc.functions()["grade"].varnames
len(pyc.functions()["grade"].code)

İlk çağrıdan önce sihirli kelimeyi yazın.ledger.cpython-314.pycVe ikisini karşılaştır.code- Sonra dene.import marshal; marshal.loads(pyc.data[16:])3.11 dosyasını çevirmenizin altında okuyun ve hatayı okuyun.

Laboratuvar B: sabit havuz zinciri

python
cls = load_class("Ledger-g.class")
cls.constant(13)
cls.constant(14)
cls.describe_constant(13)
[row["text"] for row in jvm_listing(cls, "grade")]

Takip edin .#13- ...#8ve #14Sonra da Utf8Telefon etmeden önce elden yazılar.describe_constant- O zaman yüklen .Ledger-gnone.classVe aramak .cls.method("grade").attribute_names()İkisi de.CodeArray aynı, özellikler değil.

Laboratuvar C: derinlik ve etiketler

python
module = load_wasm("ledger.wasm")
[(row["depth"], row["text"]) for row in wasm_listing(module, "grade")]
module.function_name(2)
load_wasm("ledger-names.wasm").names["locals"][2]

Bul .br_tableSırayla ve derinliğini oku.endArdından sırayı çiz ve her birini tabloda bir etiket numarası ile eşleştir.ledger.watVe bul .block $sixBu etikette 3 isim var.

Laboratuvar D: token'dan byte'e

python
asm = load_assembly("LedgerNet.dll")
asm.method("Grade")
body = asm.method_body(asm.method("Grade")["RVA"])
body.fat, body.max_stack, body.code_size, hex(body.local_signature_token)
asm.token_text(0x11000001)
[row["text"] for row in cil_listing(asm, "GradeLabel")]

RVA dosya sıfırını hesaplayın 0x2060- ....textBölüm başlığı el ile yapıp karşılaştırın asm.rva_to_offset(0x2060)O zaman yukarı bak .asm.row(0x08, 3)Ve isim nerede bul .scoreHayatları.

Pratik Laboratuvar

Karşılaştırmaya beşinci bir kaynak dili veya tabloya altıncı bir gerçek ekleyin.

Gereksinimler:

  • Bir kompili olan bir dilde bir işlevi bir döngü ve yerel değişken ile yazın, örneğin Kotlin bir sınıf dosyasına veya Rust bir Wasm modülüne.build_fixtures.sh- Evet .
  • Okurla uyumlu olan okuyucuyu her talimat için müjdeciye uygun hale getir.
  • Uzaklaştırmawhat_each_keeps()Yeni konteynerle birlikte, her konteyner için bir faktör daha: vücut bir istisna masası taşıyor mu yoksa tryBölge.
  • Yeni konteyneri figure_data()ve yeniden canlandırmak için.site/figures-re-43.js- Evet .
  • Bir okuyucu, bir gerçeği bulduğu tablo, özelliği veya bölümü isimlendirmeden kaydettikçe başarısız olan bir test yazın.

Başarılı olmak demektir .python3 code/main.py keepsHer hücre için kanıt olan yeni satır gösterir ve her oracle karşılaştırması hala aynı fikirde.

Ders Artifakti

Bu ders de içerir .outputs/skill-bytecode-and-managed-formats.md. Bir bytecode konteynerini bir başlık raporu, bir fonksiyonun kontrol edilmiş listesi ve her isim, tip ve yapıyı saklanan veya kaybedilen olarak işaretleyen bir faktör tablosuna dönüştürmek için kullanın.

Kontrol et

Demo ve Deterministik Suite'i çalıştır:

bash
cd phases/13-tools-and-protocols/43-bytecode-and-managed-formats
python3 code/main.py
npx tsx code/main.ts
python3 -m unittest discover -s code/tests -v

Verifikasyon, şunu kanıtlamalıdır:

  • Dört pyc başlığı 3495, 3531, 3571, ve 3627 sihirli kelimeleri taşıyor, bayraklar 3, 8 baytlık bir haş, ve zaman damgası dosyaları bayraklar 0, mtime 1791975565 ve boyut 497 taşıyor
  • 3.11 ve sonrası için her sihirli kelime, 2900 + 50nKural serbest bırakır.
  • Unmarshaller aynı isimleri, sabitleri, kod bytesini, değişken isimlerini ve çizgi tablosunu gönderir.marshal.loads()Dosyada çalışan tercüman yazdı
  • Dört dosyanın hepsi modül adlarını saklıyor.('score', 'bucket')içingrade, ilk satır 10 ve ledger.pyDosya adı olarak
  • grade.co_codeDört versiyonda 80, 62, 70 ve 96 byte ve kaydedilen listeler 31, 26, 26 ve 35 talimatları vardır.
  • 3.11 listesi POP_JUMP_FORWARD_IF_FALSE, 3.12 ve 3.13'te RETURN_CONST, ve 3.14'te LOAD_SMALL_INT- Evet .LOAD_FAST_BORROWveNOT_TAKEN
  • 3.14'ü-C -Olistede 13 önbelleğe girdi gösterildi grade, beş tanesi sonra .BINARY_OP, ve uyarlama listesi gösterileri COMPARE_OP_INT
  • Başlık olmadan 0d 0a, bir kısaltılmış başlık ve bir kod nesnesi olmayan bir vücut yükseltmek FormatError
  • Bir talimat listesi sadece çalışmakta olan tercüman tarafından yazılmış pyc için mevcuttur ve alınanlara eşleşir disçıkış
  • sınıf dosyalarının büyükleri 65 veya 69, boyutları 1581, 1832, 1404 ve 1581 ve sabit bir grup sayıları 82, 94, 79, ve 82
  • sabit#13karar verir Method Ledger.grade:(I)Ive #47- ...String F
  • JVM sökücü kabul ediyor javap -c -pDört yapıdaki 108 talimatın 108-nde
  • tableswitchpc 4'te düşük 6, yüksek 10, varsayılan 48, hedefleri 46, 44, 42, 40, 40 ile 36 byte
  • Üç hatalama seviyesi atribut sayısını değiştirir ve 50 kod baytını gradeAynı
  • ifne- Evet .if_icmpgevegotohedefler şubenin opkoduna göre
  • Yanlış bir sihir, geri kalan baytlar ve belirsiz bir opkod yükseltmesi.FormatError
  • Yedi Wasm bölümü yararlı yükleri karşılaştırıyor .wasm-objdump -hİsimler oluşturmak bir nameGümrük bölümü 293 ila 425
  • türleri, env.logİtkinlik, 5 ihracat ve 1024'te yazılmış olan veri bölümüledger.wat
  • Wasm dağıtıcısı onaylıyor .wasm-objdump -dHer iki yapıda da 77 talimatın 77'sinde
  • grade6 adet yuva blokları var.br_tableEtiketlerle4 4 4 4 4 4 3 2 1 0 0ve 4'ün 6'da varsayılan derinlik ve 1'in i32yerel
  • İşlev isimleri, isim bölümü olmayan ve sadece yerel isimlerle birlikte yapılan ihracatlardan gelir.
  • LEB128 e4 00100'e eşit.7f-1, 80 081024'e benzer blok türleri.wasm-objdump
  • Yanlış bir önbellek, 2. versiyon ve bilinmeyen bir opkod yükseltmesi.FormatError
  • Birleştirme PE32 makine ile 0x14c, RVA'da CLI başlığı 0x2008, 2.5, ILONLY, ve giriş noktası 0x06000005
  • Metadata versiyonu v4.0.30319, beş akıntı var ve 14 tabloda sıra sayıları ve sıra boyutları vardır Oracle raporları
  • .NET okuyucu , 246 kontrolün 246 ' sında Sistem.Reflection.Metadata ile aynı fikirde .
  • Gradeparametreye sahip scoreİmzalamastatic int32(int32), şişman bir başlık ile MaxStack2 ve 45 kod bayt ve 1 int32Adı olmayan yerel
  • CIL switchhedefler,br.s- Evet .ldsfld- Evet .callveldstrOperandlar tablolar ve yığınlar üzerinden çözülür.
  • CIL opcode tablosu her çalıştırma süresi opcodeyi kapsar ve sadece no.
  • Bu sayede, bir simgeyi oluşturmak için bir simgeyi oluşturmak için, bir simgeyi oluşturmak için bir simgeyi oluşturmak için, bir simgeyi oluşturmak için bir simgeyi oluşturmak için, bir simgeyi oluşturmak için bir simgeyi oluşturmak için, bir simgeyi oluşturmak için bir simgeyi oluşturmak için, bir simgeyi oluşturmak için bir simgeyi oluşturmak için, bir simgeyi oluşturmak için bir simgeyi oluşturmak için, bir simgeyi oluşturmak için bir simgeyi oluşturmak için, bir simgeyi oluşturmak için bir simgeyi oluşturmak için, bir simgeyi oluşturmak için bir simgeyi oluşturmak için, bir simgeyi oluşturmak için.
  • Demo'daki her oracle satırı n'den n'e kadar okunuyor.
  • Eğitimsel rakam aynı verileri içerir figure_data()üretir

Üretim Başarısızlık Modları

FailureWhat the weak tool reportsWhat the analysis must prove
Decompiler older than the interpreterUnsupported opcode, output marked incomplete, or a claim of obfuscationThe magic word, the release it names, and the tool's documented ceiling
pyc read with the wrong interpretermarshal.loads raises or returns garbageOur unmarshaller reads names, constants, and sizes from any 3.9 to 3.15 file, and instructions come from the matching dis
Specialised opcodes mistaken for the file's contentCOMPARE_OP_INT in a listingA pyc holds the unspecialised form, specialisation happens in memory after warm-up
Local names reported for a default javac buildvar1, var2 presented as source namesLocalVariableTable is present only with -g, the 50 code bytes are identical either way
Branch target off by the instruction lengthA JVM listing whose jumps land mid-instructionOffsets relative to the branch opcode, checked against javap
Switch padding skippedA tableswitch with a wrong default and shifted casesZero to three padding bytes to a 4-aligned pc, 36 bytes for five cases
Wasm names assumed stripped"no symbols"Exports name every exported function, locals need the name section, wasm-strip removes only that section
Three tools, three spellingsget_local versus local.get, a function at 0x8b versus 0x8eSame bytes, and the listing that the comparison was captured with
Table row size guessedEvery row after the first table misreadRow sizes from HeapSizes, row counts, and coded index widths, equal to the oracle's
Local names expected in a release assemblyV_0 presented as the source nameLocals have types in StandAloneSig and names only in a PDB that DebugType none did not write
Reader guesses an unknown opcode or type codeA plausible mnemonicFormatError with the offset, and the oracle comparison stops there
Oracle captured from another buildTests pass on stale textbuild_fixtures.sh names every tool version and writes versions.txt and sha256.txt

İşlem Kuralı

Başlık ilk önce okuyun ve gösterdiği üreticiyi isimlendirin. Bilmediği şeyi reddeden bir okuyucu ile konteyneri okuyun ve listesini aynı dosyadaki formatın kendi aracı ile karşılaştırın. Sonra, bir dekompiler'in çıkışında her isim ve tipli için, konteyner onu sakladı veya araç onu çıkarmıştır.

Daha Fazla Okumak

This free lesson is part of the AI Engineering from Scratch curriculum. Read the full explanation, run the lesson code, and verify the result in the interactive reader or from the repository source.

Browse the complete course catalog or open this lesson on GitHub.