Hello World dari Bytecode untuk JVM

Kami menyusun program sederhana yang menampilkan "Hello World" dan menelusuri strukturnya


Saya tidak berpikir bahwa artikel tersebut akan cukup informatif bagi mereka yang tidak tahu bagaimana tampilan bytecode dan bagaimana JVM bekerja dengannya (misalnya, setidaknya instruksi paling sederhana (pengetahuan tentang keberadaan mereka)).


Padahal, itu tidak begitu sulit. Cukup menggunakan alat javap dari JDK dan mempertimbangkan kode yang dibongkar.


Dan kita akan mulai menganalisis struktur bytecode untuk JVM


Buku yang sangat berguna untuk ini adalah spesifikasi JVM resmi - Spesifikasi Mesin Virtual Java di oracle


Untuk memulai, buat program sederhana:


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

Kompilasi dengan tim javac Main.java dan benar-benar melakukan pembongkaran


  javap -c -v Main 

Kelas Utama


 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" 

Ini hanya representasi bytecode yang lebih mudah dilihat seseorang daripada bytecode asli, tetapi tampilannya berbeda:


  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 

(Anda dapat membuka file .class Anda melalui Sublime Text yang menunjukkan File-> Save with Encoding -> Hexademical)


Kami akan bekerja dengan kode ini.


Tapi pertama-tama, kita perlu memformatnya agar tidak bingung di mana itu, dan bytecode, pada kenyataannya, memiliki struktur yang sangat kaku:


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

Anda dapat menemukannya di spesifikasi JVM Bab 4.1 Struktur ClassFile


Semuanya sederhana di sini - dimensi dalam byte ditunjukkan di sebelah kiri, dan deskripsi di sebelah kanan.


Kami akan menganalisis bytecode dalam heksadesimal, di mana setiap digit mengambil 4 bit, dan karenanya, untuk dua byte - 4 digit dan untuk empat byte - 8 digit.


sihir


sihir adalah nilai yang mengidentifikasi format kelas kami. Itu sama dengan 0xCAFEBABE , yang memiliki sejarah pembuatannya sendiri .


minor_version, major_version


Ini adalah versi file class Anda. Jika kita memanggil major_version M dan minor_version m, kita mendapatkan versi file class kita sebagai Mm


Sekarang saya akan segera memberikan contoh dari program kami "Hello World" untuk melihat bagaimana mereka digunakan:


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

Kita bisa melihatnya dalam kode yang dibongkar, tetapi sudah dalam sistem angka desimal:


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

constant_pool_count


Jumlah variabel dalam kumpulan konstan ditunjukkan di sini. Pada saat yang sama, jika Anda memutuskan untuk menulis kode dalam bytecode murni, maka Anda pasti perlu memantau nilainya, karena jika Anda menentukan nilai yang salah, seluruh program akan masuk neraka (dicentang!).


Juga, jangan lupa bahwa Anda harus menulis di sana ___ + 1 of_variables_ di ___ + 1


Total yang kami dapatkan:


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

constant_pool []


Setiap jenis variabel dalam kumpulan konstan memiliki struktur sendiri:


  cp_info { u1 tag; u1 info[]; } 

Di sini semuanya perlu dilakukan secara berurutan. Pertama-tama kita membaca tag untuk mengetahui tipe variabel dan berdasarkan tipe variabel ini kita melihat struktur apa yang nilainya selanjutnya memiliki info[]


Tabel dengan tag dapat ditemukan dalam Tabel 4.3 spesifikasi tag pool konstan .


Sebenarnya, ini tabletnya:


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

Seperti disebutkan sebelumnya, setiap tipe konstanta memiliki strukturnya sendiri.


Di sini, misalnya, adalah struktur CONSTANT_Class :


  CONSTANT_Class_info { u1 tag; u2 name_index; } 

Struktur bidang dan metode:


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

Penting untuk dicatat di sini bahwa struktur yang berbeda mungkin memiliki panjang yang berbeda.


Pertimbangkan bagian dari kode kami:


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

Jadi, kita melihat struktur konstanta dan mengetahui bahwa byte pertama dicadangkan untuk tipe konstanta. Di sini kita melihat 0a (10) - dan karenanya, itu adalah CONSTANT_Methodref


Kami melihat strukturnya:


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

Setelah satu byte untuk tag, kita membutuhkan 4 byte lagi untuk class_index dan name_and_type_index


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

Ya, kami menemukan salah satu nilai dari kumpulan konstan. Silakan. Kami melihat, 09 - berarti jenis CONSTANT_Fieldref


Kami mendapatkan:


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

Anda mungkin berpikir bahwa sebagian besar tipe memiliki bentuk yang sama, tetapi ini tidak terjadi.
Misalnya, struktur tipe berikut ini terlihat seperti CONSTANT_String :


  CONSTANT_String_info { u1 tag; u2 string_index; } 

Semua struktur ini dapat ditemukan di Bab 4.4 The Constant Pool.


Sekarang mari kita lihat apa artinya tipe di dalam info itu sendiri.


Metode yang termasuk dalam pola *_index biasanya berisi alamat dari tabel pool konstan. Misalnya, class_index untuk nilai tipe CONSTANT_Class_info , dan string_index untuk string_index CONSTANT_Utf8_info


Kita dapat melihat ini dalam kode yang dibongkar:


  #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 

Anda juga dapat menyorot representasi angka dan string.


Anda dapat membaca tentang representasi angka mulai dari bab 4.4.4 , tetapi untuk saat ini kami hanya akan menganalisis garis, karena angka tersebut tidak termasuk dalam program Hello World.


Sebenarnya, beginilah tampilan baris:


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

Misalnya, Hello World kami:


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

Parsing seluruh kumpulan konstanta bytecode, kita dapatkan:


Seluruh kumpulan konstanta
  -- [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] 

Juga, kita dapat membandingkannya dengan kode yang dibongkar:


  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 

Dengan demikian memeriksa bahwa semuanya cocok, karena pada kenyataannya javap hanya memproses bytecode ini dan menunjukkannya kepada kami dalam bentuk yang diformat.


Kolam konstan diperlukan untuk instruksi. Sebagai contoh:


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

Untuk informasi lebih lanjut tentang semua tipe dalam pool konstan, lihat Bab 4.4 Pool Konstan.


Lebih jauh dalam struktur ClassFile


access_flags


Ini adalah bitmask untuk properti modifier.


Nama benderaNilaiInterpretasi
ACC_PUBLIC0x0001Menyatakan public ; dapat diakses dari luar paketnya.
ACC_FINAL0x0010Dinyatakan final ; tidak ada subkelas yang diizinkan.
ACC_SUPER0x0020Perlakukan metode superclass secara khusus ketika diminta oleh instruksi khusus invokes .
ACC_INTERFACE0x0200Merupakan antarmuka, bukan kelas.
ACC_ABSTRACT0x0400Deklarasi abstract ; tidak boleh dipakai.
ACC_SYNTHETIC0x1000Deklarasi sintetis; tidak ada dalam kode sumber.
ACC_ANNOTATION0x2000Dinyatakan sebagai jenis anotasi.
ACC_ENUM0x4000Dinyatakan sebagai tipe enum .

kelas ini


Harus berisi alamat di kelas this . Dalam kasus kami, ini terletak di alamat 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 ... 

Perlu dicatat bahwa struktur variabel ini harus sesuai dengan CONSTANT_Class_info


super_class


Alamat leluhur kelas. Dalam kasus kami, nilainya ada di alamat #6 . Yah, struktur nilai CONSTANT_Class_info juga diperlukan


Nama-nama kelas ini didefinisikan dalam struktur konstanta CONSTANT_Utf8_info . Jika kita melihat sel #21 dan #22 , kita akan melihat:


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

Artinya, dalam sel-sel ini name_index dari struktur ditunjukkan:


  CONSTANT_Class_info { u1 tag; u2 name_index; } 

interfaces_count, fields_count


Mereka tidak ada dalam program kami, jadi nilainya akan sama dengan 0000, dan tidak akan ada nilai berikutnya dari fields[] , interfaces[] .


Baca selengkapnya 4.1 Struktur ClassFile


methods_count


Jumlah metode. Meskipun dalam kode kita melihat satu metode di kelas, sebenarnya ada dua dari mereka. Selain metode main , ada juga konstruktor default. Karena itu, jumlah mereka adalah dua, dalam kasus kami.


metode []


Setiap elemen harus sesuai dengan struktur method_info yang diuraikan dalam Bab 4.6 Metode


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

Dalam bytecode kami (diformat, dengan komentar) tampilannya seperti ini:


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

Mari kita menganalisis struktur metode secara lebih rinci:


access_flags


Topeng pengubah. Tabel 4.5 Metode akses dan bendera properti


Nama benderaNilaiInterpretasi
ACC_PUBLIC0x0001Menyatakan public ; dapat diakses dari luar paketnya.
ACC_PRIVATE0x0002Dinyatakan private ; hanya dapat diakses dalam kelas yang mendefinisikan.
ACC_PROTECTED0x0004Dinyatakan protected ; dapat diakses dalam subkelas.
ACC_STATIC0x0008Dinyatakan static .
ACC_FINAL0x0010Dinyatakan final ; tidak boleh diganti ( Β§5.4.5 ).
ACC_SYNCHRONIZED0x0020Dinyatakan synchronized ; doa dibungkus dengan penggunaan monitor.
ACC_BRIDGE0x0040Metode jembatan, yang dihasilkan oleh kompiler.
ACC_VARARGS0x0080Dideklarasikan dengan jumlah variabel argumen.
ACC_NATIVE0x0100Menyatakan native ; diimplementasikan dalam bahasa selain Jawa.
ACC_ABSTRACT0x0400Deklarasi abstract ; tidak ada implementasi yang disediakan.
ACC_STRICT0x0800Dinyatakan strictfp ; mode floating-point adalah FP-ketat.
ACC_SYNTHETIC0x1000Deklarasi sintetis; tidak ada dalam kode sumber.

Seperti yang dapat kita lihat dari bytecode, dalam metode public Main(); (konstruktor) adalah mask 0001 , yang berarti ACC_PUBLIC .


Sekarang mari kita coba merakit sendiri metode main . Inilah yang dia miliki:


  • publik - ACC_PUBLIC - 0x0001
  • statis - ACC_STATIC - 0x0008
  • String ... args - ACC_VARARGS - 0x0080

Kami mengumpulkan topeng: 0x0001 + 0x0008 + 0x0080 = 0x0089 . Jadi kami mendapat access_flag


By the way, ACC_VARARGS adalah opsional di sini, dalam arti jika kita
menggunakan String [] args sebagai ganti String ... args, maka flag ini tidak akan menjadi

name_index


Metode nama alamat ( CONSTANT_Utf8_info ) di kumpulan konstan. Penting untuk dicatat di sini bahwa nama konstruktor bukan Utama, tetapi <init> , terletak di sel # 7.


Pelajari lebih lanjut tentang <init> dan <clinit> di Bab 2.9 Metode Khusus


descriptor_index


Secara kasar, ini adalah alamat yang menunjuk ke pegangan metode. Deskriptor ini berisi jenis nilai balik dan jenis tanda tangannya.


Juga, JVM menggunakan singkatan yang ditafsirkan:


Karakter BaseTypeJenisInterpretasi
Bbytebyte yang ditandatangani
CcharTitik kode karakter Unicode di Basic Multilingual Plane, disandikan dengan UTF-16
Ddoublenilai floating-point presisi ganda
Ffloatnilai floating-point presisi tunggal
Iintbilangan bulat
Jlongbilangan bulat panjang
L ClassName ;referenceturunan dari class ClassName
Sshortditandatangani pendek
Zbooleantrue atau false
[referencesatu dimensi array

Secara umum, ini terlihat seperti ini:


  ( ParameterDescriptor* ) ReturnDescriptor 

Sebagai contoh, metode berikut:


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

Dapat direpresentasikan sebagai


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

Sebenarnya, I int , D double , dan Ljava/lang/Thread; class Thread dari perpustakaan java.lang standar.


Selanjutnya, ada atribut yang juga memiliki struktur sendiri.


Tapi pertama-tama, seperti biasa, attributes_count menghitung


Kemudian atribut diri dengan struktur yang dijelaskan dalam Bab 4.7 Atribut


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

atribut_name_index


Menentukan nama atribut. Dalam kasus kami, kedua metode memiliki Code . Atribut adalah topik besar yang terpisah di mana Anda bahkan dapat membuat atribut Anda sendiri berdasarkan spesifikasi. Tetapi untuk sekarang, kita harus tahu bahwa attribute_name_index hanya menunjuk ke alamat dalam kumpulan konstan dengan struktur CONSTANT_Utf8_info


attribute_length


Berisi panjang atribut, tidak termasuk attribute_name_index dan attribute_length


info


Selanjutnya, kita akan menggunakan struktur Code , karena dalam nilai attribute_name_index kita menunjuk ke nilai dalam kumpulan konstanta Code .


Baca lebih lanjut: Bab 4.7.3 Atribut Kode


Berikut strukturnya:


  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


Sepertinya saya bahwa nama atribut ini dapat menyesatkan karena max awalan. Bahkan, ini adalah ukuran tumpukan minimum yang diperlukan untuk menyelesaikan operasi. Nah, nama ini mengambil logika, untuk mengatakan ukuran tumpukan maksimum yang akan dicapai selama operasi.


Sederhananya, JVM akan mengalokasikan ruang untuk tumpukan operan. Di sana Anda dapat menentukan nilai yang lebih besar dari yang diperlukan, tetapi mendefinisikan nilai dalam atribut ini kurang dari yang diperlukan akan menghasilkan kesalahan.


Pada topik tumpukan, Anda dapat membaca " Pada tumpukan dan tumpukan dalam konteks dunia Java " atau di " JVM Internals "


max_locals


Ukuran maksimum variabel lokal


Anda bisa berkenalan dengan variabel lokal baik dalam Menguasai Java Bytecode di Core JVM atau di JVM Internal yang sama


code_length


Ukuran kode yang akan dieksekusi di dalam metode


kode []


Setiap kode menunjuk ke beberapa instruksi. Tabel korelasi optcode dan perintah dengan mnemonik dapat ditemukan di Wikipedia - Java bytecode daftar instruksi atau dalam spesifikasi itu sendiri di akhir buku


Misalnya, ambil konstruktor kami:


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

Di sini kita dapat menemukan kode kita:


  2a b7 00 01 b1 

Kami mencari perintah di tabel dan membandingkan:


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

Juga deskripsi perintah ini dapat ditemukan di sini: Bab 4.10.1.9. Ketik Instruksi Pemeriksaan


exception_table_length


Menentukan jumlah elemen dalam exception_table. Kami tidak memiliki kait pengecualian sehingga kami tidak akan menguraikannya. Tetapi Anda juga dapat membaca Bab 4.7.3 Atribut Kode


exception_table []


Ini memiliki struktur sebagai berikut:


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

Untuk menyederhanakan, Anda perlu menentukan awal, akhir ( start_pc , end_pc ) dari kode yang akan ditangani catch_type pengecualian catch_type


attribute_count


Jumlah Atribut dalam Code


atribut []


Atribut sering digunakan oleh penganalisa atau penentang.




Alat Bytecode


Ini bukan topik yang berhubungan dengan artikel ini, tetapi masih secara tidak langsung terkait dengannya.


Ada banyak alat untuk bekerja dengan bytecode. Di sini saya ingin meninjau Byte Code Engineering Library (BCEL) dari Apache Commons.


Untuk memulainya, dengan menggunakannya kita bisa mendapatkan beberapa atribut 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()); 

Daftar kode lengkap
 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


.


Literatur bekas


  • The Java Virtual Machine Specification β€” docs.oracle

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


All Articles