Hallo Welt von Bytecode für JVM

Wir kompilieren ein einfaches Programm, das "Hello World" anzeigt und dessen Struktur durchläuft


Ich denke nicht, dass der Artikel ausreichend informativ ist für diejenigen, die nicht oberflächlich wissen, wie der Bytecode aussieht und wie die JVM damit arbeitet (zum Beispiel die einfachsten Anweisungen (Kenntnisse über ihre Existenz)).


In der Tat ist es nicht so schwierig. Es reicht aus, das javap Tool aus dem JDK zu verwenden und den disassemblierten Code zu berücksichtigen.


Und wir werden anfangen, die Struktur des Bytecodes für die JVM zu analysieren


Ein sehr nützliches Buch dafür war die offizielle JVM-Spezifikation - The Java Virtual Machine Specification bei oracle


Erstellen Sie zunächst ein einfaches Programm:


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

Kompilieren Sie es mit dem javac Main.java Team und nehmen Sie die Demontage vor


  javap -c -v Main 

Hauptklasse


 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" 

Dies ist nur eine Bytecode-Darstellung, die für eine Person leichter zu sehen ist als der ursprüngliche Bytecode, die jedoch anders aussieht:


  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 

(Sie können Ihre .class Datei über Sublime Text öffnen, der File-> Save with Encoding -> Hexademical anzeigt.)


Wir werden mit diesem Code arbeiten.


Aber zuerst müssen wir es formatieren, um nicht zu verwechseln, wo es ist, und der Bytecode hat tatsächlich eine sehr starre Struktur:


  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]; } 

Sie finden es in der JVM-Spezifikation in Kapitel 4.1 Die ClassFile-Struktur


Hier ist alles einfach - die Dimension in Bytes ist links und die Beschreibung rechts angegeben.


Wir analysieren den Bytecode in hexadezimaler Schreibweise, wobei jede Ziffer 4 Bits benötigt, also für zwei Bytes - 4 Ziffern und für vier Bytes - 8 Ziffern.


Magie


Magie ist ein Wert, der das Format unserer Klasse kennzeichnet. Es ist gleich 0xCAFEBABE , das eine eigene Schöpfungsgeschichte hat .


minor_version, major_version


Dies sind Versionen Ihrer class . Wenn wir major_version M und minor_version m minor_version , erhalten wir die Version unserer class als Mm


Jetzt werde ich sofort Beispiele aus unserem Programm "Hello World" geben, um zu sehen, wie sie verwendet werden:


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

Wir können es im disassemblierten Code sehen, aber bereits im Dezimalzahlensystem:


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

constant_pool_count


Hier wird die Anzahl der Variablen im Konstantenpool angegeben. Zur gleichen Zeit, wenn Sie sich entscheiden, Code in reinem Bytecode zu schreiben, müssen Sie auf jeden Fall seinen Wert überwachen, denn wenn Sie den falschen Wert angeben, wird das gesamte Programm zur Hölle (überprüft!).


Vergessen Sie auch nicht, dass Sie dort die Anzahl_Variablen_ in den ___ + 1 schreiben sollten


Insgesamt bekommen wir:


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

constant_pool []


Jeder Variablentyp im Konstantenpool hat eine eigene Struktur:


  cp_info { u1 tag; u1 info[]; } 

Hier muss alles nacheinander erledigt werden. Zuerst lesen wir tag , um den Typ der Variablen herauszufinden, und anhand des Typs dieser Variablen untersuchen wir, welche Struktur ihr nachfolgender Wert info[]


Eine Tabelle mit Tags finden Sie in der Spezifikation Tabelle 4.3 Konstantenpool-Tags .


Eigentlich ist hier das Tablet:


Konstanter TypWert
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

Wie bereits erwähnt, hat jeder Konstantentyp eine eigene Struktur.


Hier ist zum Beispiel die CONSTANT_Class Struktur:


  CONSTANT_Class_info { u1 tag; u2 name_index; } 

Feld- und Methodenstruktur:


  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; } 

Hierbei ist zu beachten, dass unterschiedliche Strukturen unterschiedliche Längen haben können.


Betrachten Sie einen Teil unseres Codes:


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

Wir sehen uns also die Struktur der Konstanten an und stellen fest, dass das erste Byte für den Konstantentyp reserviert ist. Hier sehen wir 0a (10) - und daher ist es CONSTANT_Methodref


Wir betrachten seine Struktur:


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

Nach einem Byte für das Tag benötigen wir 4 weitere Bytes für class_index und name_and_type_index


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

Nun, wir haben einen der Werte des konstanten Pools gefunden. Mach weiter. Wir schauen, 09 - bedeutet den Typ CONSTANT_Fieldref


Wir bekommen:


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

Sie könnten denken, dass die meisten Typen dieselbe Form haben, aber dies ist nicht der Fall.
Beispielsweise sieht eine Struktur des folgenden Typs wie CONSTANT_String :


  CONSTANT_String_info { u1 tag; u2 string_index; } 

Alle diese Strukturen finden Sie in Kapitel 4.4 Der Konstantenpool.


Nun wollen wir sehen, was Typen in info selbst bedeuten.


Methoden, die unter das Muster *_index , enthalten normalerweise die Adresse aus der Konstantenpooltabelle. Zum Beispiel class_index für einen Wert vom Typ CONSTANT_Class_info und string_index für einen string_index CONSTANT_Utf8_info


Wir können dies im disassemblierten Code sehen:


  #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 

Sie können auch die Darstellung von Zahlen und Zeichenfolgen hervorheben.


Sie können die Darstellung von Zahlen ab Kapitel 4.4.4 nachlesen, aber wir werden vorerst nur die Zeilen analysieren, da die Zahlen noch nicht im Hello World-Programm enthalten sind


Eigentlich sieht die Zeile so aus:


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

Zum Beispiel unsere Hallo Welt:


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

Wenn wir den gesamten Pool von Bytecode-Konstanten analysieren, erhalten wir:


Der ganze Konstantenpool
  -- [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] 

Wir können es auch mit disassembliertem Code vergleichen:


  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 

Dabei wird geprüft, ob alles passt, denn tatsächlich verarbeitet javap diesen Bytecode einfach und zeigt ihn uns formatiert an.


Der konstante Pool wird für Anweisungen benötigt. Zum Beispiel:


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

Weitere Informationen zu allen Typen im Konstantenpool finden Sie in Kapitel 4.4 Der Konstantenpool.


Weiter in der ClassFile- Struktur


access_flags


Dies ist eine Bitmaske für Modifizierereigenschaften.


FahnennameWertInterpretation
ACC_PUBLIC0x0001public erklärt; kann von außerhalb des Pakets zugegriffen werden.
ACC_FINAL0x0010Für final erklärt; Keine Unterklassen erlaubt.
ACC_SUPER0x0020Behandeln Sie Methoden der Oberklasse besonders, wenn sie von der Anweisung invokespecial aufgerufen werden.
ACC_INTERFACE0x0200Ist eine Schnittstelle, keine Klasse.
ACC_ABSTRACT0x0400Deklarierte abstract ; darf nicht instanziiert werden.
ACC_SYNTHETIC0x1000Synthetisch deklariert; nicht im Quellcode vorhanden.
ACC_ANNOTATION0x2000Als Anmerkungstyp deklariert.
ACC_ENUM0x4000Als enum deklariert.

this_class


Muss eine Adresse für this Klasse enthalten. In unserem Fall befindet es sich unter der Adresse 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 ... 

Es ist zu beachten, dass die Struktur dieser Variablen CONSTANT_Class_info entsprechen muss


super_class


Adresse des Vorfahren der Klasse. In unserem Fall befindet sich der Wert an der Adresse #6 . Nun, die CONSTANT_Class_info ist ebenfalls erforderlich


Die Namen dieser Klassen sind in der Struktur der Konstante CONSTANT_Utf8_info definiert. Wenn wir uns die Zellen #21 und #22 ansehen, werden wir sehen:


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

Das heißt, in diesen Zellen wird name_index aus der Struktur angegeben:


  CONSTANT_Class_info { u1 tag; u2 name_index; } 

interfaces_count, fields_count


Sie befinden sich nicht in unserem Programm, daher sind ihre Werte gleich 0000, und es gibt einfach keine nachfolgenden Werte für fields[] , interfaces[] .


Lesen Sie mehr 4.1 Die ClassFile-Struktur


methods_count


Anzahl der Methoden. Obwohl wir im Code eine Methode in der Klasse sehen, gibt es tatsächlich zwei davon. Neben der main gibt es auch einen Standardkonstruktor. Daher ist ihre Zahl in unserem Fall zwei.


methoden []


Jedes Element muss der in Kapitel 4.6 Methoden beschriebenen method_info Struktur entsprechen


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

In unserem Bytecode (formatiert, mit Kommentaren) sieht es so aus:


  -- [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] 

Lassen Sie uns die Struktur der Methoden genauer analysieren:


access_flags


Modifikatormaske. Tabelle 4.5 Methodenzugriffs- und Eigenschaftsflags


FahnennameWertInterpretation
ACC_PUBLIC0x0001public erklärt; kann von außerhalb des Pakets zugegriffen werden.
ACC_PRIVATE0x0002Für private erklärt; Zugriff nur innerhalb der definierenden Klasse.
ACC_PROTECTED0x0004Für protected erklärt; kann innerhalb von Unterklassen zugegriffen werden.
ACC_STATIC0x0008static deklariert.
ACC_FINAL0x0010Für final erklärt; darf nicht überschrieben werden ( §5.4.5 ).
ACC_SYNCHRONIZED0x0020Deklariert synchronized ; Der Aufruf wird von einer Monitorverwendung umschlossen.
ACC_BRIDGE0x0040Eine vom Compiler generierte Bridge-Methode.
ACC_VARARGS0x0080Deklariert mit variabler Anzahl von Argumenten.
ACC_NATIVE0x0100Als native deklariert; in einer anderen Sprache als Java implementiert.
ACC_ABSTRACT0x0400Deklarierte abstract ; Eine Implementierung ist nicht vorgesehen.
ACC_STRICT0x0800strictfp ; Der Gleitkommamodus ist FP-streng.
ACC_SYNTHETIC0x1000Synthetisch deklariert; nicht im Quellcode vorhanden.

Wie wir aus dem Bytecode ersehen können, in der public Main(); -Methode public Main(); (Konstruktor) ist die Maske 0001 , was ACC_PUBLIC bedeutet.


Versuchen wir nun, die Hauptmethode selbst zusammenzusetzen. Folgendes hat er:


  • public - ACC_PUBLIC - 0x0001
  • static - ACC_STATIC - 0x0008
  • String ... args - ACC_VARARGS - 0x0080

Wir sammeln die Maske: 0x0001 + 0x0008 + 0x0080 = 0x0089 . Also haben wir access_flag


Übrigens ist ACC_VARARGS hier optional, in dem Sinne, dass wenn wir
Wenn Sie String [] args anstelle von String ... args verwenden, wird dieses Flag nicht verwendet

name_index


Methodenname Adresse ( CONSTANT_Utf8_info ) im Konstantenpool. Hierbei ist zu beachten, dass der Konstruktorname nicht Main, sondern <init> in Zelle 7 lautet.


Weitere <clinit> <init> und <clinit> in Kapitel 2.9 Spezielle Methoden


descriptor_index


Grob gesagt ist dies eine Adresse, die auf ein Methodenhandle verweist. Dieser Deskriptor enthält den Typ des Rückgabewerts und den Typ seiner Signatur.


Die JVM verwendet auch interpretierte Abkürzungen:


BaseType- ZeichenTypInterpretation
Bbytesigniertes Byte
CcharUnicode-Zeichencodepunkt in der mehrsprachigen Basisebene, codiert mit UTF-16
DdoubleGleitkommawert mit doppelter Genauigkeit
FfloatGleitkommawert mit einfacher Genauigkeit
IintGanzzahl
Jlonglange ganze Zahl
L ClassName ;referenceeine Instanz der Klasse ClassName
Sshortkurz unterschrieben
Zbooleantrue oder false
[referenceeine Array-Dimension

Im Allgemeinen sieht es so aus:


  ( ParameterDescriptor* ) ReturnDescriptor 

Zum Beispiel die folgende Methode:


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

Kann dargestellt werden als


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

Eigentlich ist I int , D ist double und Ljava/lang/Thread; class Thread aus der Standardbibliothek java.lang .


Als nächstes gibt es Attribute, die ebenfalls eine eigene Struktur haben.


Aber zuerst, wie immer, zählt dessen attributes_count


Dann die Attribute selbst mit der in Kapitel 4.7 beschriebenen Struktur


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

attribute_name_index


Gibt einen Attributnamen an. In unserem Fall haben beide Methoden einen Code . Attribute ist ein eigenständiges großes Thema, in dem Sie sogar Ihre eigenen Attribute nach Spezifikation erstellen können. Vorerst sollten wir jedoch wissen, dass attribute_name_index nur auf die Adresse im Konstantenpool mit der CONSTANT_Utf8_info Struktur verweist


attribute_length


Enthält die Länge des Attributs ohne attribute_name_index und attribute_length


info


Als Nächstes verwenden wir die Code , da wir im Wert von attribute_name_index auf den Wert im Pool der Code verwiesen haben.


Lesen Sie mehr: Kapitel 4.7.3 Das Code-Attribut


Hier ist seine Struktur:


  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


Es scheint mir, dass der Name dieses Attributs aufgrund des Präfixes max irreführend sein kann. Tatsächlich ist dies die minimale Stapelgröße, die zum Abschließen des Vorgangs erforderlich ist. Nun, dieser Name übernimmt die Logik, um die maximale Stapelgröße anzugeben, die während des Vorgangs erreicht wird.


Einfach ausgedrückt, die JVM weist dem Operandenstapel Speicherplatz zu. Dort können Sie einen Wert angeben, der größer als erforderlich ist. Wenn Sie jedoch einen Wert in diesem Attribut definieren, der kleiner als erforderlich ist, wird ein Fehler verursacht.


Zum Thema des Stacks können Sie " Auf dem Stack und Heap im Kontext der Java-Welt " oder " JVM Internals " lesen.


max_locals


Maximale Größe lokaler Variablen


Sie können sich mit lokalen Variablen entweder in Mastering Java Bytecode im Kern der JVM oder in denselben JVM- Interna vertraut machen


Codelänge


Die Größe des Codes, der innerhalb der Methode ausgeführt wird


code []


Jeder Code verweist auf eine Anweisung. Die Tabelle zur Korrelation von optcode und Befehlen mit Mnemonics finden Sie auf Wikipedia - Java-Bytecode-Anweisungslisten oder in der Spezifikation selbst am Ende des Buches


Nehmen Sie zum Beispiel unseren Konstruktor:


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

Hier finden wir unseren Code:


  2a b7 00 01 b1 

Wir suchen nach Befehlen in der Tabelle und vergleichen:


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

Beschreibungen dieser Befehle finden Sie auch hier: Kapitel 4.10.1.9. Anweisungen zur Typprüfung


Ausnahmetabellenlänge


Gibt die Anzahl der Elemente in der Ausnahmetabelle an. Wir haben keine Exception-Hooks, daher werden wir sie nicht analysieren. Sie können aber auch das Kapitel 4.7.3 Das Code-Attribut lesen


Ausnahmetabelle []


Es hat folgende Struktur:


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

Zur Vereinfachung müssen Sie den Anfang, das Ende ( start_pc , end_pc ) des Codes, den handler_pc verarbeiten soll, und den Ausnahmetyp catch_type


attributes_count


Anzahl der Attribute im Code


Attribute []


Attribute werden häufig von Analysatoren oder Debuggern verwendet.




Bytecode-Werkzeuge


Dies ist nicht das Thema, das sich auf diesen Artikel bezieht, aber es hängt immer noch indirekt damit zusammen.


Es gibt viele Tools für die Arbeit mit Bytecode. Hier möchte ich die Byte Code Engineering Library (BCEL) von Apache Commons überprüfen.


Zunächst können wir damit einige Bytecode-Attribute erhalten:


  // 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()); 

Vollständige Codeliste
 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


.


Benutzte Literatur


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


All Articles