Olá Mundo da Bytecode para JVM

Nós compilamos um programa simples que exibe "Hello World" e percorre sua estrutura


Eu não acho que o artigo seja suficientemente informativo para aqueles que não sabem superficialmente como é o bytecode e como a JVM trabalha com ele (por exemplo, pelo menos as instruções mais simples (conhecimento sobre sua existência)).


De fato, não é tão difícil. É suficiente usar a ferramenta javap do JDK e considerar o código desmontado.


E começaremos a analisar a estrutura do bytecode para a JVM


Um livro muito útil para isso foi a especificação oficial da JVM - A Especificação da Java Virtual Machine na Oracle


Para começar, crie um programa simples:


  public class Main { public static void main(String ... args) { System.out.println("Hello World"); } } 

Compile-o com a equipe javac Main.java e javac Main.java a desmontagem


  javap -c -v Main 

Main.class


 Classfile /C:/Users/Arthur/playground/java/jvm/Main.class Last modified 26.10.2019; size 413 bytes MD5 checksum 6449121a3bb611fee394e4f322401ee1 Compiled from "Main.java" public class Main minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #6.#15 // java/lang/Object."<init>":()V #2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #18 // Hello World #4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #21 // Main #6 = Class #22 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 main #12 = Utf8 ([Ljava/lang/String;)V #13 = Utf8 SourceFile #14 = Utf8 Main.java #15 = NameAndType #7:#8 // "<init>":()V #16 = Class #23 // java/lang/System #17 = NameAndType #24:#25 // out:Ljava/io/PrintStream; #18 = Utf8 Hello World #19 = Class #26 // java/io/PrintStream #20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V #21 = Utf8 Main #22 = Utf8 java/lang/Object #23 = Utf8 java/lang/System #24 = Utf8 out #25 = Utf8 Ljava/io/PrintStream; #26 = Utf8 java/io/PrintStream #27 = Utf8 println #28 = Utf8 (Ljava/lang/String;)V { public Main(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 public static void main(java.lang.String...); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello World 5: invokevirtual #4// Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 4: 0 line 5: 8 } SourceFile: "Main.java" 

Esta é apenas uma representação de bytecode que é mais fácil para uma pessoa ver do que o bytecode original, mas parece diferente:


  cafe babe 0000 0034 001d 0a00 0600 0f09 0010 0011 0800 120a 0013 0014 0700 1507 0016 0100 063c 696e 6974 3e01 0003 2829 5601 0004 436f 6465 0100 0f4c 696e 654e 756d 6265 7254 6162 6c65 0100 046d 6169 6e01 0016 285b 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b 2956 0100 0a53 6f75 7263 6546 696c 6501 0009 4d61 696e 2e6a 6176 610c 0007 0008 0700 170c 0018 0019 0100 0b48 656c 6c6f 2057 6f72 6c64 0700 1a0c 001b 001c 0100 044d 6169 6e01 0010 6a61 7661 2f6c 616e 672f 4f62 6a65 6374 0100 106a 6176 612f 6c61 6e67 2f53 7973 7465 6d01 0003 6f75 7401 0015 4c6a 6176 612f 696f 2f50 7269 6e74 5374 7265 616d 3b01 0013 6a61 7661 2f69 6f2f 5072 696e 7453 7472 6561 6d01 0007 7072 696e 746c 6e01 0015 284c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b29 5600 2100 0500 0600 0000 0000 0200 0100 0700 0800 0100 0900 0000 1d00 0100 0100 0000 052a b700 01b1 0000 0001 000a 0000 0006 0001 0000 0001 0089 000b 000c 0001 0009 0000 0025 0002 0001 0000 0009 b200 0212 03b6 0004 b100 0000 0100 0a00 0000 0a00 0200 0000 0400 0800 0500 0100 0d00 0000 0200 0e 

(Você pode abrir seu arquivo .class através de Sublime Text indicando File-> Save with Encoding -> Hexademical)


Vamos trabalhar com esse código.


Mas primeiro, precisamos formatá-lo para não confundir onde está, e o bytecode, de fato, tem uma estrutura muito rígida:


  ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; } 

Você pode encontrá-lo na especificação da JVM. Capítulo 4.1 A Estrutura do ClassFile


Tudo é simples aqui - a dimensão em bytes é indicada à esquerda e a descrição à direita.


Analisaremos o bytecode em hexadecimal, onde cada dígito ocupa 4 bits e, portanto, para dois bytes - 4 dígitos e para quatro bytes - 8 dígitos.


magia


magia é um valor que identifica o formato da nossa classe. É igual a 0xCAFEBABE , que tem seu próprio histórico de criação .


minor_version, major_version


Estas são versões do seu arquivo de class . Se chamarmos major_version M e minor_version m, obteremos a versão do nosso arquivo de class como Mm


Agora vou dar imediatamente exemplos do nosso programa "Hello World" para ver como eles são usados:


  cafe babe -- magic 0000 -- minor_version 0034 -- major_version 

Podemos vê-lo no código desmontado, mas já no sistema de números decimais:


  ... public class Main minor version: 0 major version: 52 flags: ACC_PUBLIC, ... 

constant_pool_count


O número de variáveis ​​no pool constante é indicado aqui. Ao mesmo tempo, se você decidir escrever código em puro bytecode, definitivamente precisará monitorar seu valor, porque se você especificar o valor errado, todo o programa irá para o inferno (verificado!).


Além disso, não esqueça que você deve escrever lá o ___ + 1 variáveis_ no ___ + 1


Total que obtemos:


  cafe babe -- magic 0000 0034 -- version 001d -- constant_pool_count 

constant_pool []


Cada tipo de variável no pool constante tem sua própria estrutura:


  cp_info { u1 tag; u1 info[]; } 

Aqui tudo precisa ser feito sequencialmente. Primeiro, lemos a tag para descobrir o tipo da variável e, pelo tipo dessa variável, examinamos qual estrutura seu valor subsequente possui info[]


Uma tabela com tags pode ser encontrada na Tabela 4.3 .


Na verdade, aqui está o tablet:


Tipo constanteValor
CONSTANT_Class7
CONSTANT_Fieldref9
CONSTANT_Methodref10
CONSTANT_InterfaceMethodref11
CONSTANT_String8
CONSTANT_Integer3
CONSTANT_Float4
CONSTANT_Long5
CONSTANT_Double6
CONSTANT_NameAndType12
CONSTANT_Utf81
CONSTANT_MethodHandle15
CONSTANT_MethodType16
CONSTANT_InvokeDynamic18

Como mencionado anteriormente, cada tipo de constante tem sua própria estrutura.


Aqui, por exemplo, está a estrutura CONSTANT_Class :


  CONSTANT_Class_info { u1 tag; u2 name_index; } 

Estrutura de campo e método:


  CONSTANT_Fieldref_info { u1 tag; u2 class_index; u2 name_and_type_index; } CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; } 

É importante observar aqui que estruturas diferentes podem ter comprimentos diferentes.


Considere parte do nosso código:


  cafe babe 0000 0034 001d -- constant_pool_count 0a00 0600 0f09 0010 0011 0800 12 ... 

Então, examinamos a estrutura da constante e descobrimos que o primeiro byte está reservado para o tipo de constante. Aqui vemos 0a (10) - e, portanto, é CONSTANT_Methodref


Nós olhamos para sua estrutura:


  CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; } 

Após um byte para a tag, precisamos de mais 4 bytes para class_index e name_and_type_index


  cafe babe 0000 0034 001d -- constant_pool_count 0a 0006 000f -- CONSTANT_Methodref 0900 1000 1108 0012 ... 

Bem, encontramos um dos valores da piscina constante. Vá em frente. Olhamos, 09 - significa o tipo CONSTANT_Fieldref


Temos:


  cafe babe 0000 0034 001d -- constant_pool_count 0a 0006 000f -- CONSTANT_Methodref 09 0010 0011 -- CONSTANT_Fieldref 08 0012 ... 

Você pode pensar que a maioria dos tipos tem a mesma forma, mas esse não é o caso.
Por exemplo, uma estrutura do seguinte tipo se parece com CONSTANT_String :


  CONSTANT_String_info { u1 tag; u2 string_index; } 

Todas essas estruturas podem ser encontradas no Capítulo 4.4 O pool constante.


Agora vamos ver o que os tipos dentro da própria info significam.


Os métodos que se enquadram no padrão *_index geralmente contêm o endereço da tabela de pool constante. Por exemplo, class_index para um valor do tipo CONSTANT_Class_info e string_index para um string_index CONSTANT_Utf8_info


Podemos ver isso no código desmontado:


  #1 = Methodref #6.#15 // java/lang/Object."<init>":()V #2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #18 

  0a 0006 000f -- CONSTANT_Methodref 09 0010 0011 -- CONSTANT_Fieldref 08 0012 -- CONSTANT_String 

Você também pode destacar a representação de números e seqüências de caracteres.


Você pode ler sobre a representação de números a partir do capítulo 4.4.4 , mas, por enquanto, analisaremos apenas as linhas, pois os números ainda não estão incluídos no programa Hello World


Na verdade, é assim que a linha aparece:


  CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; } 

Por exemplo, nosso Hello World:


  01 -- tag 000b -- length 48 65 6c 6c 6f 20 57 6f 72 6c 64 -- bytes[length] // H ello W orld 

Analisando todo o conjunto de constantes de bytecode, obtemos:


Todo o conjunto de constantes
  -- [Constant Pool] -- methodref 0a 0006 000f -- fieldref 09 0010 0011 -- string 08 0012 -- methodref 0a 0013 0014 -- Class 07 0015 -- Class 07 0016 -- Utf8 01 0006 3c 69 6e 69 74 3e -- Utf8 01 0003 28 29 56 -- Utf8 01 0004 43 6f 64 65 -- Utf8 01 000f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 -- Utf8 01 0004 6d 61 69 6e -- Utf8 01 0016 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 29 56 -- Utf8 01 000a 53 6f 75 72 63 65 46 69 6c 65 -- Utf8 01 0009 4d 61 69 6e 2e 6a 61 76 61 -- NameAndType 0c 0007 0008 -- Class 07 0017 -- NameAndType 0c 0018 0019 -- Utf8 01 000b 48 65 6c 6c 6f 20 57 6f 72 6c 64 -- Class 07 001a -- NameAndType 0c 001b 001c -- Utf8 01 0004 4d 61 69 6e -- Utf8 01 0010 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 -- Utf8 01 0010 6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d -- Utf8 01 0003 6f 75 74 -- Utf8 01 0015 4c 6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d 3b -- Utf8 01 0013 6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d -- Utf8 01 0007 70 72 69 6e 74 6c 6e -- Utf8 01 0015 28 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 29 56 -- [Constant Pool END] 

Além disso, podemos compará-lo com o código desmontado:


  Constant pool: #1 = Methodref #6.#15 // java/lang/Object."<init>":()V #2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #18 // Hello World #4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #21 // Main #6 = Class #22 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 main #12 = Utf8 ([Ljava/lang/String;)V #13 = Utf8 SourceFile #14 = Utf8 Main.java #15 = NameAndType #7:#8 // "<init>":()V #16 = Class #23 // java/lang/System #17 = NameAndType #24:#25 // out:Ljava/io/PrintStream; #18 = Utf8 Hello World #19 = Class #26 // java/io/PrintStream #20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V #21 = Utf8 Main #22 = Utf8 java/lang/Object #23 = Utf8 java/lang/System #24 = Utf8 out #25 = Utf8 Ljava/io/PrintStream; #26 = Utf8 java/io/PrintStream #27 = Utf8 println #28 = Utf8 (Ljava/lang/String;)V 

Assim, verificando se tudo corresponde, porque na verdade o javap simplesmente processa esse bytecode e o mostra para nós de forma formatada.


O pool constante é necessário para obter instruções. Por exemplo:


  public Main(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 //    1    4: return 

Para obter mais informações sobre todos os tipos no pool constante, consulte o Capítulo 4.4 O pool constante.


Indo além na estrutura ClassFile


access_flags


Esta é uma máscara de bits para propriedades do modificador.


Nome da bandeiraValorInterpretação
ACC_PUBLIC0x0001public declarado; pode ser acessado de fora de seu pacote.
ACC_FINAL0x0010final declarado; não são permitidas subclasses.
ACC_SUPER0x0020Trate os métodos de superclasse especialmente quando invocados pela instrução invokepecial .
ACC_INTERFACE0x0200É uma interface, não uma classe.
ACC_ABSTRACT0x0400abstract declarado; não deve ser instanciado.
ACC_SYNTHETIC0x1000Sintético declarado; não está presente no código fonte.
ACC_ANNOTATION0x2000Declarado como um tipo de anotação.
ACC_ENUM0x4000Declarado como um tipo de enum .

this_class


Deve conter um endereço this classe. No nosso caso, ele está localizado no endereço 5:


  Constant pool: #1 = Methodref #6.#15 // java/lang/Object."<init>":()V #2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #18 // Hello World #4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #21 // Main #6 = Class #22 // java/lang/Object ... 

Note-se que a estrutura dessa variável deve estar em conformidade com CONSTANT_Class_info


super_class


Endereço do ancestral da classe. No nosso caso, o valor está no endereço #6 . Bem, a estrutura de valor CONSTANT_Class_info também é necessária


Os nomes dessas classes são definidos na estrutura da constante CONSTANT_Utf8_info . Se olharmos para as células #21 e #22 , veremos:


  ... #21 = Utf8 Main #22 = Utf8 java/lang/Object ... 

Ou seja, nessas células, name_index da estrutura é indicado:


  CONSTANT_Class_info { u1 tag; u2 name_index; } 

interfaces_contagem, campos_contagem


Eles não estão no nosso programa, portanto, seus valores serão iguais a 0000 e simplesmente não haverá valores subsequentes dos fields[] , interfaces[] .


Leia mais 4.1 A estrutura ClassFile


methods_count


Número de métodos. Embora no código vejamos um método na classe, na verdade existem dois deles. Além do método main , também há um construtor padrão. Portanto, o número deles é dois, no nosso caso.


métodos []


Cada elemento deve estar em conformidade com a estrutura method_info descrita no Capítulo 4.6 Métodos


  method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; } 

No nosso bytecode (formatado, com comentários), fica assim:


  -- [methods] -- public Main(); 0001 --access_flags 0007 -- name_index 0008 -- descriptor_index 0001 -- attributes_count -- attribute_info 0009 -- attribute_name_index (Code) 0000 001d - attribute_length 0001 -- max_stack 0001 -- max_locals 0000 0005 -- code_length 2a b7 00 01 b1 -- code[] 0000 -- exception_table_length 0001 -- attributes_count; 000a -- attribute_name_index 0000 0006 -- attribute_length 00 01 00 00 00 01 -- public static void main(java.lang.String...); 0089 --access_flags 000b -- name_index 000c -- descriptor_index 0001 -- attributes_count -- attribute_info 0009 -- attribute_name_index (Code) 0000 0025 -- attribute_length 0002 -- max_stack 0001 -- max_locals 0000 0009 -- code_length b2 00 02 12 03 b6 00 04 b1 -- code[] 0000 -- exception_table_length 0001 -- attributes_count 000a -- attribute_name_index 0000 000a -- attribute_length 00 02 00 00 00 04 00 08 00 05 -- [methods END] 

Vamos analisar a estrutura dos métodos em mais detalhes:


access_flags


Máscara modificadora. Tabela 4.5 Acesso a método e sinalizadores de propriedade


Nome da bandeiraValorInterpretação
ACC_PUBLIC0x0001public declarado; pode ser acessado de fora de seu pacote.
ACC_PRIVATE0x0002Declarado private ; acessível somente dentro da classe de definição.
ACC_PROTECTED0x0004Declarado protected ; pode ser acessado dentro de subclasses.
ACC_STATIC0x0008static declarado.
ACC_FINAL0x0010final declarado; não deve ser substituído ( §5.4.5 ).
ACC_SYNCHRONIZED0x0020Declarado synchronized ; A chamada é agrupada por um uso do monitor.
ACC_BRIDGE0x0040Um método de ponte, gerado pelo compilador.
ACC_VARARGS0x0080Declarado com número variável de argumentos.
ACC_NATIVE0x0100native declarado; implementado em uma linguagem diferente de Java.
ACC_ABSTRACT0x0400abstract declarado; nenhuma implementação é fornecida.
ACC_STRICT0x0800strictfp declarado; modo de ponto flutuante é FP-estrito.
ACC_SYNTHETIC0x1000Sintético declarado; não está presente no código fonte.

Como podemos ver no bytecode, no método public Main(); (construtor) é a máscara 0001 , o que significa ACC_PUBLIC .


Agora vamos tentar montar o método main . Aqui está o que ele tem:


  • público - ACC_PUBLIC - 0x0001
  • estático - ACC_STATIC - 0x0008
  • String ... args - ACC_VARARGS - 0x0080

Coletamos a máscara: 0x0001 + 0x0008 + 0x0080 = 0x0089 . Então, nós temos access_flag


A propósito, ACC_VARARGS é opcional aqui, no sentido de que se nós
usado String [] args em vez de String ... args, esse sinalizador não seria

name_index


Endereço do nome do método ( CONSTANT_Utf8_info ) no pool constante. É importante observar aqui que o nome do construtor não é Main, mas <init> , localizado na célula # 7.


Aprenda mais sobre <init> e <clinit> no Capítulo 2.9 Métodos especiais


descriptor_index


Grosso modo, este é um endereço que aponta para um identificador de método. Este descritor contém o tipo do valor de retorno e o tipo de sua assinatura.


Além disso, a JVM usa abreviações interpretadas:


Caractere BaseTypeTipoInterpretação
Bbytebyte assinado
CcharPonto de código de caractere Unicode no plano multilíngue básico, codificado com UTF-16
Ddoublevalor de ponto flutuante de precisão dupla
Ffloatvalor de ponto flutuante de precisão única
Iintinteiro
Jlonginteiro longo
L ClassName ;referenceuma instância da classe ClassName
Sshortassinado curto
Zbooleantrue ou false
[referenceuma dimensão de matriz

Em geral, é assim:


  ( ParameterDescriptor* ) ReturnDescriptor 

Por exemplo, o seguinte método:


  Object method(int i, double d, Thread t) {..} 

Pode ser representado como


  (IDLjava/lang/Thread;)Ljava/lang/Object 

Na verdade, I sou int , D é double e Ljava/lang/Thread; classe Thread da biblioteca java.lang padrão.


Em seguida, existem atributos que também têm sua própria estrutura.


Mas primeiro, como sempre, sua contagem de attributes_count


Em seguida, os próprios atributos com a estrutura descrita no Capítulo 4.7 Atributos


  attribute_info { u2 attribute_name_index; u4 attribute_length; u1 info[attribute_length]; } 

attribute_name_index


Especifica um nome de atributo. No nosso caso, ambos os métodos têm um Code . Atributos é um grande tópico separado, no qual você pode criar seus próprios atributos por especificação. Mas, por enquanto, devemos saber que attribute_name_index apenas aponta para o endereço no pool constante com a estrutura CONSTANT_Utf8_info


attribute_length


Contém o comprimento do atributo, não incluindo attribute_name_index e attribute_length


informação


Em seguida, usaremos a estrutura Code , porque no valor de attribute_name_index apontamos para o valor no conjunto de constantes de Code .


Leia mais: Capítulo 4.7.3 O atributo Code


Aqui está sua estrutura:


  Code_attribute { u2 attribute_name_index; u4 attribute_length; u2 max_stack; u2 max_locals; u4 code_length; u1 code[code_length]; u2 exception_table_length; { u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; } exception_table[exception_table_length]; u2 attributes_count; attribute_info attributes[attributes_count]; } 

max_stack


Parece-me que o nome desse atributo pode ser enganoso devido ao prefixo max. De fato, esse é o tamanho mínimo da pilha necessário para concluir a operação. Bem, esse nome assume lógica, para dizer o tamanho máximo da pilha que será atingido durante a operação.


Simplificando, a JVM alocará espaço para a pilha de operandos. Lá, você pode especificar um valor maior que o necessário, mas definir um valor nesse atributo menor que o necessário resultará em um erro.


No tópico da pilha, você pode ler " Na pilha e na pilha no contexto do mundo Java " ou em " JVM Internals "


max_locals


Tamanho máximo de variáveis ​​locais


Você pode se familiarizar com variáveis ​​locais no Mastering Java Bytecode no Core da JVM ou nos mesmos JVM Internals


code_length


O tamanho do código que será executado dentro do método


código []


Cada código aponta para alguma instrução. A tabela de correlação de optcode e comandos com mnemônicos pode ser encontrada na Wikipedia - listagens de instruções de bytecode Java ou na própria especificação no final do livro


Por exemplo, considere nosso construtor:


  -- public Main(); 0001 --access_flags 0007 -- name_index 0008 -- descriptor_index 0001 -- attributes_count -- attribute_info 0009 -- attribute_name_index (Code) 0000 001d - attribute_length 00 01 -- max_stack 00 01 -- max_locals 00 00 00 05 -- code_length 2a b7 00 01 b1 -- code[] 0000 -- exception_table_length 0001 -- attributes_count; 00 0a -- attribute_name_index 0000 0006 -- attribute_length 00 01 00 00 00 01 

Aqui podemos encontrar o nosso código:


  2a b7 00 01 b1 

Procuramos comandos na tabela e comparamos:


  2a - aload_0 b7 0001 - invokespecial #1 b1 - return 

Também podem ser encontradas descrições desses comandos aqui: Capítulo 4.10.1.9. Instruções de verificação de tipo


exception_table_length


Especifica o número de elementos na tabela exception_table. Não temos ganchos de exceção, portanto não o analisaremos. Mas você também pode ler o Capítulo 4.7.3.


exception_table []


Possui a seguinte estrutura:


  { u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; } 

Para simplificar, você precisa especificar o início, o fim ( start_pc , end_pc ) do código que handler_pc manipulará e o catch_type exceção catch_type


attribute_count


Número de atributos no Code


atributos []


Os atributos são frequentemente usados ​​por analisadores ou depuradores.




Ferramentas Bytecode


Este não é o tópico relacionado a este artigo, mas ainda está indiretamente relacionado a ele.


Existem muitas ferramentas para trabalhar com o bytecode. Aqui eu gostaria de revisar a Biblioteca de Engenharia de Código de Byte (BCEL) do Apache Commons.


Para começar, usando-o, podemos obter alguns atributos de bytecode:


  // read file from resources/complied/ClassA.class InputStream inputStream = ClassParserExample.class.getResourceAsStream("/compiled/ClassA.class"); if (inputStream == null) throw new FileNotFoundException(); ClassParser parser = new ClassParser(inputStream, "ClassA.class"); JavaClass clazz = parser.parse(); final String HEX_BYTECODE = getHex(clazz.getBytes()); System.out.println("Hex bytecode: "); System.out.println(HEX_BYTECODE); System.out.println(); final String MINOR_VER = getHex(clazz.getMinor()); final String MAJOR_VER = getHex(clazz.getMajor()); final String CONSTANT_POOL = getHex(clazz.getConstantPool().getConstantPool()); final String ACCESS_FLAGS = getHex(clazz.getAccessFlags()); final String THIS_CLASS = getHex(clazz.getClassName().getBytes()); final String SUPER_CLASS = getHex(clazz.getSuperClass().getBytes()); final String INTERFACES = getHex(clazz.getInterfaces()); final String FIELDS = getHex(clazz.getFields()); final String METHODS = getHex(clazz.getMethods()); final String ATTRIBUTES = getHex(clazz.getAttributes()); 

Listagem de código completo
 import org.apache.bcel.classfile.*; import org.apache.commons.codec.binary.Hex; import java.io.*; public class ClassParserExample { public static void main(String... args) throws IOException, ClassNotFoundException { // read file from resources/complied/ClassA.class InputStream inputStream = ClassParserExample.class.getResourceAsStream("/compiled/ClassA.class"); if (inputStream == null) throw new FileNotFoundException(); ClassParser parser = new ClassParser(inputStream, "ClassA.class"); JavaClass clazz = parser.parse(); final String HEX_BYTECODE = getHex(clazz.getBytes()); System.out.println("Hex bytecode: "); System.out.println(HEX_BYTECODE); System.out.println(); final String MINOR_VER = getHex(clazz.getMinor()); final String MAJOR_VER = getHex(clazz.getMajor()); final String CONSTANT_POOL = getHex(clazz.getConstantPool().getConstantPool()); final String ACCESS_FLAGS = getHex(clazz.getAccessFlags()); final String THIS_CLASS = getHex(clazz.getClassName().getBytes()); final String SUPER_CLASS = getHex(clazz.getSuperClass().getBytes()); final String INTERFACES = getHex(clazz.getInterfaces()); final String FIELDS = getHex(clazz.getFields()); final String METHODS = getHex(clazz.getMethods()); final String ATTRIBUTES = getHex(clazz.getAttributes()); System.out.println( "minor: " + MINOR_VER ); // 0 System.out.println( "major: " + MAJOR_VER ); // 34 System.out.println( "constant pool: " + CONSTANT_POOL); // not correctly System.out.println( "access flags: " + ACCESS_FLAGS ); // 21 System.out.println( "this class: " + THIS_CLASS ); System.out.println( "super class: " + SUPER_CLASS ); // Object System.out.println( "interfaces: " + INTERFACES ); // <empty> System.out.println( "fields: " + FIELDS ); // <empty> System.out.println( "methods: " + METHODS ); // one method: psvm hello world System.out.println( "attributes: " + ATTRIBUTES ); // 536f7572636546696c65 - I think it's instructions for Java tools } private static String getHex(byte[] bytes){ return Hex.encodeHexString(bytes); } private static String getHex(int intNum){ return Integer.toHexString(intNum); } private static String getHex(Constant[] constants){ if (constants == null) return null; StringBuilder sb = new StringBuilder(); for (Constant c : constants){ if (c == null) continue; sb.append(getHex(c.getTag())).append(" "); } return sb.toString(); } private static String getHex(JavaClass[] clazzes){ if (clazzes == null) return null; StringBuilder sb = new StringBuilder(); for (JavaClass c : clazzes){ sb.append(getHex(c.getClassName().getBytes())).append(" "); } return sb.toString(); } private static String getHex(Field[] fields){ if (fields == null) return null; StringBuilder sb = new StringBuilder(); for (Field c : fields){ sb.append(getHex(c.getName().getBytes())).append(" "); } return sb.toString(); } private static String getHex(Method[] methods){ if (methods == null) return null; StringBuilder sb = new StringBuilder(); for (Method c : methods){ sb.append(getHex(c.getName().getBytes())).append(" "); } return sb.toString(); } private static String getHex(Attribute[] attributes){ if (attributes == null) return null; StringBuilder sb = new StringBuilder(); for (Attribute c : attributes){ sb.append(getHex(c.getName().getBytes())).append(" "); } return sb.toString(); } } /* Class A: public class ClassA { public static void main(String[] args) { System.out.println("Hello world"); } } */ /* Class A bytecode: cafe babe 0000 0034 0022 0a00 0600 1409 0015 0016 0800 170a 0018 0019 0700 1a07 001b 0100 063c 696e 6974 3e01 0003 2829 5601 0004 436f 6465 0100 0f4c 696e 654e 756d 6265 7254 6162 6c65 0100 124c 6f63 616c 5661 7269 6162 6c65 5461 626c 6501 0004 7468 6973 0100 1d4c 636f 6d2f 6170 706c 6f69 6478 7878 2f70 6172 7365 2f43 6c61 7373 413b 0100 046d 6169 6e01 0016 285b 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b 2956 0100 0461 7267 7301 0013 5b4c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b01 000a 536f 7572 6365 4669 6c65 0100 0b43 6c61 7373 412e 6a61 7661 0c00 0700 0807 001c 0c00 1d00 1e01 000b 4865 6c6c 6f20 776f 726c 6407 001f 0c00 2000 2101 001b 636f 6d2f 6170 706c 6f69 6478 7878 2f70 6172 7365 2f43 6c61 7373 4101 0010 6a61 7661 2f6c 616e 672f 4f62 6a65 6374 0100 106a 6176 612f 6c61 6e67 2f53 7973 7465 6d01 0003 6f75 7401 0015 4c6a 6176 612f 696f 2f50 7269 6e74 5374 7265 616d 3b01 0013 6a61 7661 2f69 6f2f 5072 696e 7453 7472 6561 6d01 0007 7072 696e 746c 6e01 0015 284c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b29 5600 2100 0500 0600 0000 0000 0200 0100 0700 0800 0100 0900 0000 2f00 0100 0100 0000 052a b700 01b1 0000 0002 000a 0000 0006 0001 0000 0006 000b 0000 000c 0001 0000 0005 000c 000d 0000 0009 000e 000f 0001 0009 0000 0037 0002 0001 0000 0009 b200 0212 03b6 0004 b100 0000 0200 0a00 0000 0a00 0200 0000 0800 0800 0900 0b00 0000 0c00 0100 0000 0900 1000 1100 0000 0100 1200 0000 0200 13 */ /* Assembled code: Classfile /C:/java/BCEL/src/main/resources/compiled/ClassA.class Last modified 08.12.2019; size 563 bytes MD5 checksum bcd0198f6764a1dc2f3967fef701452e Compiled from "ClassA.java" public class com.apploidxxx.parse.ClassA minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #6.#20 // java/lang/Object."<init>":()V #2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #23 // Hello world #4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #26 // com/apploidxxx/parse/ClassA #6 = Class #27 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 Lcom/apploidxxx/parse/ClassA; #14 = Utf8 main #15 = Utf8 ([Ljava/lang/String;)V #16 = Utf8 args #17 = Utf8 [Ljava/lang/String; #18 = Utf8 SourceFile #19 = Utf8 ClassA.java #20 = NameAndType #7:#8 // "<init>":()V #21 = Class #28 // java/lang/System #22 = NameAndType #29:#30 // out:Ljava/io/PrintStream; #23 = Utf8 Hello world #24 = Class #31 // java/io/PrintStream #25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V #26 = Utf8 com/apploidxxx/parse/ClassA #27 = Utf8 java/lang/Object #28 = Utf8 java/lang/System #29 = Utf8 out #30 = Utf8 Ljava/io/PrintStream; #31 = Utf8 java/io/PrintStream #32 = Utf8 println #33 = Utf8 (Ljava/lang/String;)V { public com.apploidxxx.parse.ClassA(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 6: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/apploidxxx/parse/ClassA; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello world 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 8: 0 line 9: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String; } SourceFile: "ClassA.java */ 

, (, Jasmin) -.



, Jasmin . , , , JVM -.


:


Hello World
 .bytecode 52.0 .source Main.j .class public Main .super java/lang/Object .method public static main([Ljava/lang/String;)V .limit stack 2 .limit locals 2 getstatic java/lang/System/out Ljava/io/PrintStream; ldc "Hello world!" invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V return .end method 

 ; ClassCreating.j .bytecode 52.0 .source ClassCreating.java .class public ClassCreating .super java/lang/Object .method public <init>()V .limit stack 1 .limit locals 1 .line 1 0: aload_0 1: invokespecial java/lang/Object/<init>()V 4: return .end method .method public static main([Ljava/lang/String;)V ; Flag ACC_VARARGS set, see JVM spec .limit stack 2 .limit locals 3 .line 3 0: new java/lang/String 3: dup 4: invokespecial java/lang/String/<init>()V 7: astore_1 .line 4 8: new ClassCreating 11: dup 12: invokespecial ClassCreating/<init>()V 15: astore_2 .line 5 16: aload_2 17: invokevirtual ClassCreating/sayHello()V .line 6 20: return .end method .method public sayHello()V .limit stack 2 .limit locals 1 .line 9 0: getstatic java/lang/System/out Ljava/io/PrintStream; 3: ldc "Hello, User!" 5: invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V .line 10 8: return .end method 



Hello World


- : gist.github


.


Literatura usada


Source: https://habr.com/ru/post/pt480550/


All Articles