Phase 13: Tools & Protocols

Além do código nativo: Python, JVM, .NET e WebAssembly Bytecode

Um arquivo de código de byte é um programa mais as notas do compilador sobre isso, e esta lição constrói os leitores que mostram quais notas cada recipiente guarda.

Type: Build

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

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

Time: ~150 minutes

Objetivos de aprendizagem

  • Leia o 16 bytes .pyccabeçalho do PEP 552 e desmarca o objeto do código sem o marshalModulo, para ficheiros escritos por quatro versões de intérprete.
  • Parsear um arquivo de classe JVM a partir do JVMS Capítulo 4: pool constante, campos, métodos, o Codeatributo, e as tabelas que javac -g- E acrescenta.
  • Desmontar os códigos opcodes JVM em Ledger.classe provar a listagem contrajavap -cEm quatro edifícios da mesma fonte.
  • Leia um módulo WebAssembly secção por secção, decodifique cada corpo de função e prove o resultado contra wasm-objdump -d- Não .
  • Passear os metadados ECMA-335 de um conjunto .NET do diretório de dados PE para o MethodDefAs linhas e o CIL de cada método, verificados com base na biblioteca de leitores que a ILSpy utiliza.
  • Medir, para uma função em cinco recipientes, quais nomes, tipos e estrutura de controle cada formato mantém e quais perdem.

O problema

Construíste .ledger.pye encontrouledger.cpython-314.pycem __pycache__Perguntaste ao agente o que o ficheiro contém, o agente executou um decompiler.Unsupported opcodeE uma linha que dizia:# WARNING: Decompyle incompleteO agente concluiu que o ficheiro foi obfuscado.

O arquivo não foi obfuscado.2b 0eO agente não leu o cabeçalho, por isso não conseguiu distinguir.

A lição 33 mostrou o que o código nativo perde: nomes de parâmetros, tipos e osswitchque se tornou uma mesa de saltos. Os contêineres de código de byte perdem menos, e cada um perde algo diferente. Um pyc mantém todos os nomes variáveis e nenhum tipo. Um arquivo de classe guarda tipos e mantém nomes locais apenas quando você pede. Um módulo de Wassm mantém a estrutura de controle e deixa cair nomes, a menos que uma seção personalizada os carregue. Uma montagem .NET mantém nomes de parâmetros em uma tabela e deixa nomes locais em um arquivo separado. Não se pode julgar a saída de um decompiler em qualquer um deles até saber com que notas o recipiente teve que começar.

Esta lição cria quatro leitores na biblioteca padrão Python. Cada um lê o contêiner a partir de seu primeiro byte, lista uma função e é verificado contra a própria ferramenta do formato em fixtures que você construiu.gradedeledgerlado a lado nos cinco recipientes.

O cabeçalho da pyc é de quatro palavras

O PEP 552 define o cabeçalho como quatro palavras de 32 bits. O primeiro é o número mágico. O segundo é um campo de bits. Set de bits 0 significa que o arquivo é baseado em hash. Set de bits 1 significa check_sourceCom bit 0 limpo, as palavras três e quatro são o mtime da fonte e o tamanho da fonte. Com bit 0 conjunto, eles possuem um SipHash de 64 bits da fonte.

O número mágico vive emInclude/internal/pycore_magic_number.hA regra desde 3.11 é que o Python 3.n começa em 2900 + 50n, e cada alteração de código de byte dentro de um ciclo de liberação adiciona um. O token armazenado é o número de 16 bits seguido por \r\nPython 3.15.0 foi lançado em 2026-10-09 e não está instalado na máquina que construiu essas instalações, então seu número vem do arquivo de cabeçalho e não de um arquivo aqui.

Os aparelhos contêm dois ficheiros pyc por intérprete. compile_pyc.pyEscreveu-as com py_compile.compile(..., dfile="ledger.py")para que o nome do arquivo armazenado seja ledger.pyOs primeiros 16 bytes dos arquivos 3.14:

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

Leia o cabeçalho do timestamp palavra por palavra. 03 00 00 00ou 00 00 00 00É a palavra da bandeira.8d 60 cf 6aé 0x6acf608d = 1791975565, o mtime de ledger.pyQuando o ficheiro foi criado. f1 01 00 00O arquivo de hash verificado tem bandeiras 3 e oito bytes hash em vez disso. Os quatro intérpretes concordam em tudo no cabeçalho exceto a palavra mágica:

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()em code/main.pyRetorna estes campos e nomeia a versão da palavra. importlib.util.MAGIC_NUMBERdá os mesmos quatro bytes para o intérprete em execução. python3 code/main.py comparecompila a fonte com cada python3.11- Não .python3.15Encontrado emPATHPara cada um, imprime o cabeçalho que o nosso leitor vê ao lado dos fatos que o intérprete relata sobre si mesmo.

Marshal escreve o código Objeto Campo por Campo

O byte 16 de cada dispositivo é e3- Isso é .0x80 | ord('c'): o código de tipo marshal para um objeto de código com FLAG_REFconjunto, que diz ao leitor para lembrar o objeto para mais tarde rAs referências. A documentação do Marshal diz que o formato é desdocumentado de propósito e que os objetos de código não são compatíveis entre as versões. Ambas as declarações são verdadeiras, e o formato ainda é pequeno o suficiente para ser lido. Python/marshal.cEnumera os códigos de tipo: NNenhum.ium int de 32 bits, sbytes, zE ...ZCartas curtas ASCII, (E ...)- Duplos,cum objeto de código, rOs contêineres reservam o seu espaço de referência antes dos filhos, para que o leitor faça o mesmo.

w_complex_object()escreve um objeto de código em ordem fixa. para 3.11 a 3.15 éargcount- Não .posonlyargcount- Não .kwonlyargcount- Não .stacksize- Não .flagscomo 32 bits de inches, então code- Não .consts- Não .names- Não .localsplusnames- Não .localspluskinds- Não .filename- Não .name- Não .qualnameComo objetos, então firstlineno, entãolinetableE ...exceptiontableDecodificar o código do módulo objeto do arquivo 3.14 à mão a partir do byte 17:

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 ...

Os bytes de código são dois por instrução: 80 00É o que éRESUME 0- Não .52 06É o que éLOAD_CONST 6- Não .74 00É o que éSTORE_NAME 0, e 17 00É o que éMAKE_FUNCTIONA lista 3.14 nas instalações imprime as mesmas cinco declarações de nível de módulo.codevêm as constantes, que contêm os quatro objectos de código aninhados, e depois deles names- Não .('LABELS', 'percent_change', 'grade', 'total', 'grade_label')- Não .

Unmarshallerem code/main.pyLeia este layout e o mais antigo que 3.9 e 3.10 usaram, onde nlocals- Não .varnames- Não .freevars, e cellvarsO que é que se passa?CodeObjectcom os mesmos nomes de atributos que o objeto real tem. varnamesfiltroslocalsplusnamespor o CO_FAST_LOCALUm pouco .localspluskinds- Para o ...gradeEm cada um dos quatro arquivos que ele retorna .('score', 'bucket')- Não .firstlineno10 e um não vazio.linetableO teste .test_unmarshaller_agrees_with_the_running_interpretercompara cada campo com marshal.loads()Os outros três arquivos são legíveis pelo nosso código e não legíveis pormarshal- Sob este intérprete.

Quatro intérpretes, quatro bytecodes para classificação

A desdocumentação apresenta um aviso em cada versão: o bytecode é um detalhe de implementação do CPython e não há garantia de que ele permaneça o mesmo.python3.1N -m dis ledger.pyFoi capturado para cada intérprete.

Em 3.11 gradeComeça assim:

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_OPO segundo byte está localizado no offset 6 e a próxima instrução em 10. Os dois bytes entre eles são uma entrada de cache de PEP 659, o intérprete adaptativo especializado. COMPARE_OPO 16 é seguido por 22, por isso tem duas entradas no cache.show_caches=True. 3.12 substitui LOAD_CONST- E mais .RETURN_VALUEcomRETURN_CONST, renomear o salto para POP_JUMP_IF_FALSE, e os psicólogos .gradeDe 80 a 62 bytes. 3,13 impressões de rótulos L1- Não .L4Em vez de compensar e dar o CLI -Cpara caches e -Opara compensações. 3.14 remove RETURN_CONST- Mais uma vez, acrescenta.LOAD_SMALL_INTE ...LOAD_FAST_BORROW, e inserções NOT_TAKENapós cada salto condicional:

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

O -C -Olistagem mostra 13 CACHEEntradas dentro grade- Cinco anos depois .BINARY_OP, um após o outro .COMPARE_OP, um após o outro .POP_JUMP_IF_FALSEO dispositivo adaptativo mostra para que são os caches.dis.dis(grade, adaptive=True)impressões digitaisRESUME_CHECKE ...COMPARE_OP_INTonde a listagem simples tinha RESUMEE ...COMPARE_OP, e total- EspetáculosFOR_ITER_LIST- Não .BINARY_OP_ADD_INT, e JUMP_BACKWARD_NO_JITAs formas especializadas vivem no mesmoco_codeUma pyc nunca as contém, porque a especialização acontece na memória.

É por isso que os decompiladores têm limites de versão. uncompyle6 3.9.3 diz que aceita código de byte de Python 1.0 a 3.8. decompyle3 3.9.3 lê 3.7 e 3.8. xdis 6.3.0, o desmonteador de versão cruzada por trás de ambos, lê até 3.13. pycdc tem tabelas de código optico até python_3_13.cppno seu arquivo de construção, e em uma impressão de código op desconhecido Unsupported opcodePyLingual reivindica todas as versões lançadas desde 3.6 e usa intérpretes de 3.8 a 3.15 para recompilar e verificar sua própria saída. Para um arquivo 3.14 os três primeiros rejeitam ou falham, e apenas os dois últimos aplicam-se. Leia a palavra mágica antes de escolher uma ferramenta.

O arquivo de classe mantém um dicionário próprio

O JVMS Java SE 25, secção 4.1, dá o layout como uma estrutura: magic- Não .minor_version- Não .major_version- Não .constant_pool_count, a piscina constante,access_flags- Não .this_class- Não .super_classInterfaces, campos, métodos, atributos.0xCAFEBABEUma máquina virtual Java SE 25 aceita versões principais 45 a 69, e uma menor de 65535 marca um arquivo de pré-visualização.Ledger-g.classsãoca fe ba be 00 00 00 41 00 5e: maior 65 é Java 21, e 0x5e= 94 é o número constante de poças. Ledger-jdk25.class, construído por javac 25.0.2 a partir da mesma fonte, começa ca fe ba be 00 00 00 45- Major 69.

A piscina constante é onde o arquivo de classe mantém seu próprio dicionário.#13No-gconstrução é um Methodrefque aponta para a classe #8e nome e tipo #14. Seguindo os índices dá Ledger.grade:(I)I, que é exatamente o que javap -vimpressões digitais depois //O descriptor .(I)IÉ um .intParâmetro e um intResultado.([I)I- Não .totaldiz que o parâmetro é um intarray. Cada método, campo e classe que o código toca tem tal entrada, e o verificador na seção 4.10 recusa um arquivo cujas instruções discordam com eles.ClassFileclasse em code/main.pylê todas as 17 etiquetas constantes da tabela 4.4 e trata um Longou DoubleEle aumenta em uma etiqueta desconhecida e em bytes após o último atributo.

Atributos dependem da classe, cada campo, cada método e o CodeJVMS diz que a máquina virtual deve ignorar atributos que não conhece, e é por isso que as tabelas de depuração podem ser adicionadas ou retiradas sem mudar um corpo de método.-ggera todas as informações de depuração, incluindo as variáveis locais, que o padrão gera números de linha e apenas o arquivo fonte, e que -g:noneAs três construem medidas que:

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

Os 50 bytes de código de gradeOs três arquivos são idênticos e o teste verifica-o.-gadiciona para gradeÉ um .LocalVariableTablelinha: início 0, comprimento 50, slot 0, nome score, descriptor ISem ele , um decompiler vê o slot 0 de tipo .intE tem de inventar um nome.StackMapTablepermanece em cada construção porque o verificador precisa dele, e o BootstrapMethodsO atributo permanece porque mainconcatenar cordas através de invokedynamic- Não .

Descifrar comutador de tabela de mão

O capítulo 6 do JVMS define cada instrução como um byte de opcode e os operandos que nomeam opcode.tableswitchE ...lookupswitchComecem com 0 a 3 bytes de enchimento para que os operandos de 32 bits começem em um endereço que é um múltiplo de 4.

Os bytes de gradede pc 4 em Ledger-g.class- Não .

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

Trinta e seis bytes, uma instrução e tudo.switch (score / 10)O processo de construção do mesmo.switchNa lição 33 precisava de um controlo de limites, um lea, a movslq, um addAqui os alvos estão na instrução, e também o fato de que eles pertencem juntos.

disassemble_jvm()abrange todos os 202 códigos opcodes definidos com as suas largura de operand, segue a regra do empolgação e torna operands o caminho javap- Não.compare_with_javap()Parse os capturados .javap -c -ptexto, incluindo o texto multilínea tableswitchO teste é feito com a utilização de um bloco de comando, e verifica a compensação, a mnemônica e o operando para cada instrução.test_branch_offsets_are_relative_to_the_branch_instructionChequesifnena pc 1 aterragem em 6, if_icmpgeA 13 de aterragem no 33, e gotoA 30 horas, aterrissando em 10.

O radar2 6.2.4, construído a partir da fonte para estas lições, lê o mesmo arquivo. rabin2 -IRelatórios arch java- Não .lang java 21- Não .linenum true, e lsyms true, que é a leitura da versão e das duas tabelas de depuração. pd 14 @ sym.Ledger.gradeImprima o tableswitchCom flechas para os seus cinco alvos.Unable to parse code attribute: 2O documento de informação é um documento que é um dos principais elementos de informação sobre o processo de elaboração de um documento.javapcomo o oráculo. Os decompiladores para este formato são maduros em comparação com o Python. CFR 0.152 é licenciado pelo MIT. Procyon 0.6.0 e Vineflower 1.12.0 são Apache-2.0, e Vineflower é um garfo Fernflower com suporte Java 21. Fernflower vive dentro do repositório da comunidade IntelliJ. O JADX 1.5.6 cobre o formato Android DEX, e o JD-GUI teve sua última versão em 2019. Harrand, Soto-Valero, Monperrus e Baudry mediram oito deles em 2020. A melhor produziu uma saída sintática correta para 84% das classes e semânticamente equivalente para 78%. O seu meta-descompiler recuperou 37,6% das classes que nenhuma ferramenta manteve. Tipos, nomes e pilhas verificadas tornam o formato decompilável. Não fazem um único decompiler completo.

Um módulo de esgoto é uma lista de seções

A especificação central define um módulo como o preâmbulo 00 61 73 6d 01 00 00 00, a corda .\0asmE versão 1, seguida de secções. Cada seção é um byte id, um u32Id 1 a 13 são as seções conhecidas, cada uma no máximo uma vez e em ordem. Id 0 é uma seção personalizada com um nome, permitido em qualquer lugar. Todos os números inteiros são LEB128, por isso e4 00é de 100 e 80 08É 1024.wasm-objdump -h- Não .ledger.wasmO artigo 1.o do Tratado define sete secções e WasmModuleencontra os mesmos sete com os mesmos deslocamentos de carga útil:

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

A divisão entre as seções Função e Código existe para que um navegador possa começar a compilar antes de o arquivo inteiro chegar.gradeO vector local é 01 7f: um grupo de um i32- Isso é .bucket, com o seu tipo e sem o seu nome.

O fluxo de controlo é estruturado por construção.block- Não .loop, ou ifAs etiquetas são indexadas por profundidade de nidificação. br_tableO sistema de transferência de dados é um sistema de transferência de dados que permite a transferência de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dados de dadosgradeem arquivo de compensação 0xa3- Não .

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

Cada rótulo é uma profundidade.block, e o código após o seuendÉ o que éi32.const 4seguida por br 4O resultado é o número 6.blockinstruções diante da mesa, das onze etiquetas e das cinco brinstruções depois de ser o todo switch, e um validador rejeita qualquer ramo que saia da estrutura. disassemble_wasm()acompanha a profundidade, por isso os ramos de listagem como wasm-objdump, abrange cada código op de um único byte de unreachable- Não .i64.extend32_s, e coincide com os capturados .wasm-objdump -dEm 77 das 77 instruções para ambas as construções.

Os nomes são o único que um módulo simples deixa cair.wasm-objdumpimpressões ainda<grade>porque a seção Export mapeia essa cadeia para o índice de função 2, e o leitor faz o mesmo.wat2wasm --debug-namesadiciona uma seção personalizada chamada nameCom subseções para o nome do módulo, nomes de funções e nomes locais. O arquivo cresce para 425 bytes. O leitor encontra {0: 'score', 1: 'bucket'}para a função 2 e {0: 'values', 1: 'count', 2: 'sum', 3: 'i'}para a função 3. wasm-stripO repositório de convenções de ferramentas descreve mais dois portadores..debug_*Seções de aluguel.sourceMappingURLA seção custom pode apontar para um mapa fonte cujos números de coluna são deslocados em byte para o binário.

A ferramenta foi movida durante 2026.wasm-decompile, e a construção Homebrew nesta máquina tem exatamente as onze ferramentas restantes.print- Não .objdump, e demangle. Binaryen versão_133 fornece wasm-disO radar 6.2.4 também lê o módulo.rabin2 -IDiz:arch wasmÉ o símbolo degradeComeça em0x8e, onde o vetor local começa, enquanto wasm-objdumpetiquetas a função em 0x8bA sua listagem usa a mnemônica mais antiga.get_local- Não .set_local, e brtable 11 4 4 ...com a contagem do rótulo impressa. Os bytes são os mesmos em todas as três ferramentas e as ortografias diferem, por isso uma comparação deve nomear a ferramenta com que foi capturada. A pesquisa para esta lição não encontrou nenhum módulo de processador WebAssembly no diretório de processadores da Ghidra, e essa listagem não foi lida em sua totalidade. O plugin ghidra-wasm v2.4.0 de terceiros visa Ghidra 12.0. A especificação em si é um documento vivo. A página do W3C para wasm-core-2é um projecto de recomendação de candidato de 13 de Outubro de 2026, cujo resumo descreve a versão 3.0, e o projecto de redação tem a mesma data.

Os metadados ECMA-335 são a própria base de dados do programa

Um conjunto .NET é um arquivo PE. LedgerNet.dllComeça com MZ, tem três secções denominadas .text- Não ..rsrc, e .reloc, e declara máquina 0x14cO diretório de dados 14 do cabeçalho opcional, o cabeçalho de execução CLR, aponta para RVA 0x2008. A ECMA-335 II.25.3.3 define os 72 bytes que se encontram:

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

A raiz de metadados em RVA 0x2178Começa com a assinatura.42 53 4a 42, que diz:BSJB, depois a versão 1.1 e a cadeia v4.0.30319Cinco cabeçalhos de fluxo seguem:#~em compensação0x6c, então#Strings- Não .#US- Não .#GUID, e #BlobO II.24.2 diz que cada tipo de corrente ocorre no máximo uma vez.#~O fluxo começa com um HeapSizesbyte, zero aqui, então cada cadeia, GUID e índice de manchas é de dois bytes de largura, e um 64 bits ValidMascara que nomeia as tabelas presentes.

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

Um tamanho de linha não é armazenado em nenhum lugar. II.22 dá a cada coluna um tipo, e II.24.2.6 dá a regra de tamanho.HeapSizes. Um índice de tabela simples é de dois bytes, enquanto que essa tabela tem menos de 65536 linhas. Um índice codificado que pode apontar para n tabelas com bits de tag b é de dois bytes, enquanto cada uma dessas tabelas tem menos de 2^(16-b) linhas. CliAssemblyO sistema.Reflexão.Dumagem de metadados nas instalações informa os mesmos tamanhos e os mesmos deslocamentos de tabela, que é como o teste prova a aritmética.

III.1.9 define o token: o alto byte é a tabela, os três bytes baixos são a linha baseada em 1. 0x06000002É o que éMethodDeflinha 2. Essa linha tem RVA 0x2060, nomeGrade, e uma mancha de assinatura .00 01 08 08: convenção de chamada padrão, um parâmetro, retorno int32, parâmetro int32É o meu .ParamListpontos na linha 3 de Param, cujo nome é scoreA tabela do Param é por isso que a ILSpy pode imprimir .Grade(int score)de uma construção de libertação despojada.0x2060O corpo do método começa com um cabeçalho de gordura:

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

StandAloneSiglinha 1 é válida 07 01 08- Não .LOCAL_SIG, um local,int32O local tem um tipo e não tem nome. ECMA-335 mantém os nomes locais no formato de intercâmbio de defeitos da Partilha V, e o SDK .NET os escreve para um PDB. Este dispositivo foi construído com DebugTypeNenhum, portanto, nenhuma mesa e nenhum arquivo tem o nome.bucket- Não .PercentChangetem um cabeçalho pequeno em vez disso: seu primeiro byte 3aÉ o que é0x0e << 2 | 2, um corpo de 14 bytes sem habitantes locais e uma pilha padrão de 8.

Os 45 bytes de código de GradeDecodificar com a tabela de código optico da Parte III. switché opcode 0x45seguida por um u32Conto e tantos outros.int32Objetivos relativos à instrução após a tabela:

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

O compilador subtraiu 6 antes do switchO caso 6 é o primeiro alvo.disassemble_cil()representa tokens através das tabelas: ldsfld Labels- Não .call Grade- Não .call System.Console::WriteLine, e ldstr ' 'do #USO oráculo para tudo isto éMetaDump, um curto programa C# nos aparelhos construídos emSystem.Reflection.MetadataImprime o número de filas de cada tabela, tamanho e offset, RVA de cada método, assinatura, nomes de parâmetros e bytes IL, e os 226 campos de System.Reflection.Emit.OpCodes- Não .compare_with_metadump()O quadro de tempo de execução tem 218 códigos opcodes mais oitoprefixA tabela da lição tem 219 entradas porque incluino.do ponto III.2.2, que a tabela de emissões nunca teve.

Ferramentas para o formato: ILSpy 11.0 (MIT, lançado 2026-08-12) é executado no macOS através de sua versão de desktop Avalonia. ilspycmd11.1.0.9782 Impressões IL com -ile seleciona um método por token com -m 0x06000002. dnSpyEx 6.6.0 continua dnSpy apenas no Windows.ildasmNavio como pacotes NuGet em vez de dentro do SDK. radar2 6.2.4 lê os metadados também: rabin2 -IDiz:arch cilE ...class PE32, e islistas Ledger.Grade(int:score):intO sistema foi construído com o .NET SDK 8.0.205 porque é o SDK instalado na máquina de construção. .NET 10 está atual desde 2025-11-11 e escreve as mesmas estruturas ECMA-335.

O que cada recipiente guarda

  • Não .gradeDe todos os cinco recipientes numa mesa.python3 code/main.py keeps, que passa os leitores sobre os aparelhos:
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

A tabela explica os resultados do decompiler que você verá.int total(const int *, int)com o int total(long param_1, uint param_2)que Ghidra inferido a partir de código nativo, porque o código nativo não mantém nenhuma dessas colunas. Um pyc mantém todos os nomes e nenhum tipo, e seu fluxo de controle é saltos cuja forma muda a cada versão, então os decompiladores Python quebrar por versão e recuperar nomes de graça. Um arquivo de classe mantém tipos e pilhas verificadas, mantém nomes quando-gO que é que é o "Centro deswitchUm módulo de Wassm mantém a estrutura por construção, por isso wasm2watUma montagem .NET mantém a descrição mais completa, porque as tabelas de metadados são normativas, e ainda precisa de um PDB para o nome de um local.

A tabela diz o que o decompiler teve que trabalhar com, e quais de suas saídas copiar um fato armazenado e que inferir um. Um nome de parâmetro na saída ILSpy é uma cópia. Um nome local na mesma saída é uma inferência a menos que um PDB estivesse presente.

Construí-lo

Executa a demonstração:

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

A saída é um relatório JSON. Para cada pyc ele dá os dois cabeçalhos, o arquivo e gradetamanhos, a contagem de instruções e nomes de opcodes da lista capturada, e se o intérprete em execução pode lê-lo. Para cada classe construir ele dá a versão, tamanhos, contagens de tabela debug, e o javapPara cada edifício de Wassm, ele fornece secções, tipos, funções, conteúdo de nome-secção e oswasm-objdumpPara a montagem, o sistema fornece o cabeçalho CLI, os fluxos, os números de filas de tabela, os métodos e o sistema. Reflexão.Contrato de metadados.PATH- Não .

Leia code/main.pyna ordem seguinte:

  1. ReaderDetém o cursor e as larguraes inteiras. uleb()E ...sleb()São os leitores LEB128 que o Wasm e o .NET precisam.
  2. read_pyc_header()- Não .Unmarshaller, e PycFileLeia um PC.parse_dis_text()Leia os capturados .disListagem de dados para que a comparação entre versões não precise de quatro intérpretes no momento do ensaio.
  3. ClassFilelê o arquivo da classe e disassemble_jvm()Descreve o código.parse_javap_code()E ...compare_with_javap()são os cheques do Oráculo.
  4. WasmModulelê as secções e disassemble_wasm()Decodifica uma expressão com a sua profundidade.compare_with_wasm_objdump()É o cheque do oráculo.
  5. CliAssemblycaminha PE, cabeçalho CLI, raiz de metadados, fluxos e tabelas. TABLE_SCHEMASE ...CODED_INDEXESsão II.22 e II.24.2.6 como dados. disassemble_cil()Decodifica um corpo e compare_with_metadump()- Verifica.
  6. NativeObjectLeia o ELF suficiente para listar símbolos de função e toma a lição 33 como seu texto. what_each_keeps()E ...figure_data()construindo a comparação.

Há mais cinco comandos.python3 code/main.py pyc FILE- Não .class FILE- Não .wasm FILE, e dll FILEimprimir a estrutura e uma lista de cada função num ficheiro que lhes fornecer. python3 code/main.py comparecompilações ledger.pyCom todos os intérpretes em .PATHe imprime o que cada um escreveu.

Usá-lo

Use os leitores nesta ordem num recipiente que construiu.

  1. Leia os primeiros bytes e nome o formato.0d 0aé um pyc, ca fe ba beum arquivo de classe, 00 61 73 6dum módulo, 4d 5acom o diretório de dados 14 um conjunto de configuração.
  2. Leia o cabeçalho e anote a versão de produtor que ele chama. Para um pyc que é a palavra mágica, para um arquivo de classe a versão principal, para uma montagem as cadeias de versão de tempo de execução e metadados.
  3. Verifique todos os decompiladores que pretende executar contra essa versão antes de executá-la.
  4. Execute o leitor e a própria ferramenta do formato no mesmo arquivo e compare-os linha por linha. Uma desajuste é um bug no leitor ou uma mudança na ferramenta, e qualquer um deles deve ser entendido antes que a listagem seja evidência.
  5. Preencha a tabela de mantém para a função que você se importa, marque cada fato como conservado ou perdido e nomeie a tabela, atributo ou seção de onde veio.
  6. Só depois ler a saída do decompilador, marcar cada nome e digitar nele como uma cópia de um fato mantido ou como uma inferência.

Mantenha os arquivos de texto do oráculo com os contêineres. Quando um intérprete, JDK, SDK ou versão wabt mudar, reconstruir com build_fixtures.she recapturar, porque uma listagem alterada é uma fixação alterada.

Laboratório Interativo

Lab A: ler o cabeçalho, depois o objeto do código

  • Docodediretório, Python aberto:
bash
cd phases/13-tools-and-protocols/43-bytecode-and-managed-formats/code
python3 -q
  • Correr .
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)

Escreva a palavra mágica antes da primeira chamada.ledger.cpython-314.pycE comparar os dois.codeEntão, tente.import marshal; marshal.loads(pyc.data[16:])No arquivo 3.11 sob o seu intérprete e leia o erro.

Laboratório B: a cadeia constante da piscina

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")]

Segue-me .#13- Não .#8E ...#14, depois para oUtf8- As inscrições, à mão antes de ligar.describe_constantEntão carrega .Ledger-gnone.classe ligar .cls.method("grade").attribute_names()- Em ambos.CodeA matriz é a mesma, os atributos não são.

Laboratório C: profundidade e rótulos

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]

Encontrar obr_table- E depois, se você quiser, leia a profundidade.endAs linhas seguidas e combinar cada uma com um número de rótulo na tabela.ledger.wate encontrar oblock $sixEsse rótulo tem 3 nomes.

Laboratório D: de token para bytes

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")]

Calcule o deslocamento de arquivo de RVA 0x2060do .textcabeçalho de secção à mão e comparar com asm.rva_to_offset(0x2060)Então olha para cima .asm.row(0x08, 3)e encontrar onde o nome score- As vidas.

Laboratório de Prática

Adicione uma quinta linguagem fonte à comparação, ou um sexto fato à tabela.

Requisitos:

  • Escreva uma função com um loop e uma variável local em uma linguagem cujo compilador você tem, por exemplo Kotlin para um arquivo de classe ou Rust para um módulo Wasm. Compromete a saída e a listação do oráculo com o comando exato em build_fixtures.sh- Não .
  • Faça com que o leitor correspondente concorde com o oráculo em todas as instruções.
  • Extensãowhat_each_keeps()O novo recipiente, e um facto adicional para cada recipiente: se o corpo transporta uma mesa de excepção ou tryRegião.
  • Adicionar o novo recipiente para figure_data()e regenerar.site/figures-re-43.js- Não .
  • Escreva um teste que falha quando um leitor relata um fato como mantido sem nomear a tabela, atributo ou seção que o mantém.

O sucesso significapython3 code/main.py keepsmostra a nova linha com evidências para cada célula, e cada comparação do oráculo ainda concorda.

Lição Artifacto

Esta lição inclui outputs/skill-bytecode-and-managed-formats.md. Use-o para transformar um contêiner de código de byte em um relatório de cabeçalho, uma lista verificada de uma função e uma tabela de fatos que marca cada nome, tipo e estrutura como mantidos ou perdidos. Requer a própria ferramenta do formato como um oráculo, uma verificação de versão antes de qualquer decompilador e uma declaração de onde cada fato perdido teria vivido.

Verifique

Execute a demonstração e a suíte determinista:

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

A verificação deve provar:

  • Os quatro cabeçalhos pyc carregam palavras mágicas 3495, 3531, 3571, e 3627, bandeiras 3, e um hash de 8 bytes, e os arquivos de timestamp carregam bandeiras 0, mtime 1791975565, e tamanho 497
  • Cada palavra mágica para 3.11 e mais tarde fica na faixa do 2900 + 50nregra dá a sua liberação
  • O unmarshaler retorna os mesmos nomes, constantes, bytes de código, nomes de variáveis e tabela de linhas como marshal.loads()no arquivo o intérprete em execução escreveu
  • Os quatro arquivos guardam os nomes dos módulos.('score', 'bucket')Paragrade, primeira linha 10, e ledger.pycomo nome do arquivo
  • grade.co_codeÉ 80, 62, 70 e 96 bytes em todas as quatro versões e as listas capturadas têm 31, 26, 26 e 35 instruções
  • A lista 3.11 tem POP_JUMP_FORWARD_IF_FALSE, 3.12 e 3.13 têm RETURN_CONST, e 3.14 tem LOAD_SMALL_INT- Não .LOAD_FAST_BORROW, e NOT_TAKEN
  • O 3.14 -C -Olista mostra 13 entradas no cache em gradeCinco deles depois .BINARY_OP, e os programas de listagem adaptativa COMPARE_OP_INT
  • Um cabeçalho sem 0d 0a, um cabeçalho truncado, e um corpo que não é um objeto de código levantar FormatError
  • uma lista de instruções existe apenas para o pyc escrito pelo intérprete em execução e corresponde ao capturado dissaída
  • Os arquivos de classe têm maior 65 ou 69, tamanhos 1581, 1832, 1404 e 1581, e contos constantes de pool 82, 94, 79, e 82
  • constante#13resolve Method Ledger.grade:(I)IE ...#47- Não .String F
  • O desmontagem da JVM concorda com javap -c -pem 108 das 108 instruções em todas as quatro construções
  • tableswitchno pc 4 é 36 bytes com baixo 6, alto 10, padrão 48, e metas 46, 44, 42, 40, 40
  • Os três níveis de depuração alteram os números de atributos e deixam os 50 bytes de código de gradeidênticos
  • ifne- Não .if_icmpge, e gotoOs objetivos são relativos ao código optic da filial
  • Uma mágica errada, bytes atrasados e um aumento indefinido de código op.FormatError
  • As sete secções de Wassm têm o deslocamento da carga útil .wasm-objdump -himpressões, e os nomes de construção adiciona um nameSecção de aluguel, 293 a 425.
  • - os tipos, osenv.logimportação, as cinco exportações e o segmento de dados em 1024 decodificação, conforme escrito em ledger.wat
  • O desmonteador de Wassm concorda com wasm-objdump -dem 77 das 77 instruções em ambas as construções
  • gradetem 6 blocos aninhados, um br_tablecom rótulos 4 4 4 4 4 4 3 2 1 0 0e padrão 4 na profundidade 6, e um i32local
  • Os nomes das funções vêm de exportações sem uma secção de nome e nomes locais apenas com ele
  • LEB128 e4 00É 100, 7fé -1, 80 08é 1024, e tipos de blocos render como wasm-objdump
  • Um preâmbulo errado, versão 2, e um aumento desconhecido do código op.FormatError
  • O conjunto é PE32 com máquina 0x14c, cabeçalho CLI na RVA 0x2008, tempo de execução 2,5, ILONLY, e ponto de entrada 0x06000005
  • A versão de metadados é v4.0.30319, existem cinco correntes, e as 14 tabelas têm as linhas contadas e os tamanhos das linhas que o oráculo relata
  • O leitor .NET concorda com System.Reflection.Metadata em 246 de 246 verificações
  • Gradetem parâmetro score, assinatura static int32(int32), um cabeçalho gordo com MaxStack2 e 45 bytes de código, e um int32local sem nome
  • CIL switchObjetivos, br.s- Não .ldsfld- Não .call, e ldstroperands resolvem através das tabelas e montões
  • A tabela de códigos opticos do CIL abrange todos os códigos opticos de execução e somente adiciona no.
  • A tabela de manutenção informa o nome do parâmetro em quatro recipientes e em nenhum para o código nativo, e tipos em três
  • Cada linha do oráculo na demonstração lê n de n
  • A figura interativa incorpora os mesmos dados figure_data()produz

Modos de falha de produção

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

Regra de funcionamento

Leia o cabeçalho primeiro e nomear o produtor que ele aponta. Leia o contêiner com um leitor que recusa o que ele não sabe, e verifique sua listagem contra a própria ferramenta do formato no mesmo arquivo.

Mais leitura

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.