Inisialisasi dan pengoperasian juru kode bytecode di JVM HotSpot di bawah x86

Hampir setiap pengembang Java tahu bahwa program yang ditulis dalam Java pada awalnya dikompilasi menjadi bytecode JVM dan disimpan sebagai file kelas dalam format standar . Setelah mendapatkan file-kelas seperti itu di dalam mesin virtual dan sampai kompiler belum mencapai mereka, JVM menafsirkan bytecode yang terkandung dalam file-file kelas ini. Artikel ini memberikan tinjauan umum tentang bagaimana interpreter bekerja sehubungan dengan OpenJDK JVM HotSpot.


Isi artikel:


  • Lingkungan
  • Menjalankan aplikasi java
  • Inisialisasi juru bahasa dan kontrol transfer ke kode java
  • Contoh

Lingkungan


Untuk percobaan, kami menggunakan perakitan revisi OpenJDK JDK12 terbaru yang tersedia dengan konfigurasi autoconf


--enable-debug --with-native-debug-symbols=internal 

pada Ubuntu 18.04 / gcc 7.4.0.


--with-native-debug-symbols=internal -imbol --with-native-debug-symbols=internal berarti bahwa, ketika membangun JDK, simbol debazh akan terkandung dalam binari itu sendiri.


--enable-debug - bahwa biner akan berisi kode debug tambahan.


Membangun JDK 12 di lingkungan seperti itu bukanlah proses yang rumit. Yang perlu saya lakukan adalah menginstal JDK11 ( untuk membangun JDK n, JDK n-1 diperlukan ) dan mengirimkan secara otomatis perpustakaan yang diperlukan untuk autoconf. Selanjutnya, jalankan perintah


 bash configure --enable-debug --with-native-debug-symbols=internal && make CONF=fastdebug images 

dan setelah menunggu sedikit (di laptop saya sekitar 10 menit), kita mendapatkan fastdebug build JDK 12.


Pada prinsipnya, cukup menginstal jdk dari repositori publik dan juga mengirimkan paket openjdk-xx-dbg dengan simbol debug, di mana xx adalah versi jdk, tetapi rakitan fastdebug menyediakan fungsi debugging dari gdb yang dapat membuat hidup lebih mudah dalam beberapa kasus. Saat ini, saya aktif menggunakan ps () , sebuah fungsi untuk melihat jejak Java stack dari gdb, dan pfl () , sebuah fungsi untuk menganalisis tumpukan frame (sangat nyaman ketika men-debug penerjemah dalam gdb).


Contoh ps () dan pfl ()

Misalnya, perhatikan skrip gdb berikut


 #   java file /home/dmitrii/jdk12/build/linux-x86_64-server-fastdebug/images/jdk/bin/java #   SEGV-, HotSpot #  SEGV  . #, https://hg.openjdk.java.net/jdk/jdk12/file/06222165c35f/src/hotspot/cpu/x86/vm_version_x86.cpp#l361 handle SIGSEGV nostop noprint set breakpoint pending on set pagination off #  ,   #    # java- public static void main(String args[]) b PostJVMInit thread 2 commands #   , #    set $buf = (char *) malloc(1000) #        #(   ) b *AbstractInterpreter::_entry_table[0] thread 2 commands #      rbx. #   Method* set $mthd = ((Method *) $rbx) #    $buf call $mthd->name_and_sig_as_C_string($buf, 1000) # ,  public static void main(String args) if strcmp()("Main.main([Ljava/lang/String;)V", $buf) == 0 #   ,      # ps/pfl        #(    ps/pfl) b InterpreterRuntime::build_method_counters(JavaThread*, Method*) commands #  ,    #   delete breakpoints call ps() call pfl() c end end c end c end r -cp /home/dmitrii/jdk12/ Main 

Hasil menjalankan skrip tersebut adalah:


 "Executing ps" for thread: "main" #1 prio=5 os_prio=0 cpu=468,61ms elapsed=58,65s tid=0x00007ffff001b800 nid=0x5bfa runnable [0x00007ffff7fd9000] java.lang.Thread.State: RUNNABLE Thread: 0x00007ffff001b800 [0x5bfa] State: _running _has_called_back 0 _at_poll_safepoint 0 JavaThread state: _thread_in_Java 1 - frame( sp=0x00007ffff7fd9920, unextended_sp=0x00007ffff7fd9920, fp=0x00007ffff7fd9968, pc=0x00007fffd828748b) Main.main(Main.java:10) "Executing pfl" for thread: "main" #1 prio=5 os_prio=0 cpu=468,83ms elapsed=58,71s tid=0x00007ffff001b800 nid=0x5bfa runnable [0x00007ffff7fd9000] java.lang.Thread.State: RUNNABLE Thread: 0x00007ffff001b800 [0x5bfa] State: _running _has_called_back 0 _at_poll_safepoint 0 JavaThread state: _thread_in_Java [Describe stack layout] 0x00007ffff7fd99e0: 0x00007ffff7fd9b00 #2 entry frame call_stub word fp - 0 0x00007ffff7fd99d8: 0x00007ffff7fd9c10 call_stub word fp - 1 0x00007ffff7fd99d0: 0x00007fffd8287160 call_stub word fp - 2 0x00007ffff7fd99c8: 0x00007fffbf1fb3e0 call_stub word fp - 3 0x00007ffff7fd99c0: 0x000000000000000a call_stub word fp - 4 0x00007ffff7fd99b8: 0x00007ffff7fd9ce8 call_stub word fp - 5 0x00007ffff7fd99b0: 0x00007ffff7fd9a80 call_stub word fp - 6 0x00007ffff7fd99a8: 0x00007ffff001b800 call_stub word fp - 7 0x00007ffff7fd99a0: 0x00007ffff7fd9b40 call_stub word fp - 8 0x00007ffff7fd9998: 0x00007ffff7fd9c00 call_stub word fp - 9 0x00007ffff7fd9990: 0x00007ffff7fd9a80 call_stub word fp - 10 0x00007ffff7fd9988: 0x00007ffff7fd9ce0 call_stub word fp - 11 0x00007ffff7fd9980: 0x00007fff00001fa0 call_stub word fp - 12 0x00007ffff7fd9978: 0x0000000716a122b8 sp for #2 locals for #1 unextended_sp for #2 local 0 0x00007ffff7fd9970: 0x00007fffd82719f3 0x00007ffff7fd9968: 0x00007ffff7fd99e0 #1 method Main.main([Ljava/lang/String;)V @ 0 - 1 locals 1 max stack 0x00007ffff7fd9960: 0x00007ffff7fd9978 interpreter_frame_sender_sp 0x00007ffff7fd9958: 0x0000000000000000 interpreter_frame_last_sp 0x00007ffff7fd9950: 0x00007fffbf1fb3e0 interpreter_frame_method 0x00007ffff7fd9948: 0x0000000716a11c40 interpreter_frame_mirror 0x00007ffff7fd9940: 0x0000000000000000 interpreter_frame_mdp 0x00007ffff7fd9938: 0x00007fffbf1fb5e8 interpreter_frame_cache 0x00007ffff7fd9930: 0x00007ffff7fd9978 interpreter_frame_locals 0x00007ffff7fd9928: 0x00007fffbf1fb3d0 interpreter_frame_bcp 0x00007ffff7fd9920: 0x00007ffff7fd9920 sp for #1 interpreter_frame_initial_sp unextended_sp for #1 

Seperti yang Anda lihat, dalam kasus ps() kita baru saja mendapatkan stack panggilan, dalam kasus pfl() - organisasi stack penuh.


Menjalankan aplikasi java


Sebelum melanjutkan ke diskusi interpreter secara langsung, kami akan meninjau secara singkat tindakan yang dilakukan sebelum mentransfer kontrol ke kode java. Misalnya, ambil program Java yang "tidak melakukan apa-apa":


 public class Main { public static void main(String args[]){ } } 

dan coba cari tahu apa yang terjadi ketika Anda menjalankan aplikasi seperti itu:


javac Main.java && java Main


Hal pertama yang harus dilakukan untuk menjawab pertanyaan ini adalah menemukan dan melihat java binary - yang kami gunakan untuk menjalankan semua aplikasi JVM kami. Dalam kasus saya, ini terletak di sepanjang jalan


/home/dmitrii/jdk12/build/linux-x86_64-server-fastdebug/images/jdk/bin/java .


Tetapi pada akhirnya, tidak ada yang istimewa untuk ditonton. Ini adalah biner yang, bersama dengan simbol debazhnymi hanya membutuhkan 20KB dan dikompilasi dari hanya satu peluncur file sumber / main.c.


Yang dia lakukan adalah menerima argumen baris perintah (char * argv []), membaca argumen dari variabel lingkungan JDK_JAVA_OPTIONS , melakukan preprocessing dan validasi dasar (misalnya, Anda tidak dapat menambahkan opsi terminal atau nama kelas utama ke variabel lingkungan ini) dan memanggil fungsi JLI_Launch dengan daftar argumen yang dihasilkan.


Definisi fungsi JLI_Launch tidak terkandung dalam java binary dan, jika Anda melihat dependensi langsungnya:


 $ ldd java linux-vdso.so.1 (0x00007ffcc97ec000) libjli.so => /home/dmitrii/jdk12/build/linux-x86_64-server-fastdebug/images/jdk/bin/./../lib/libjli.so (0x00007ff27518d000) // <---------    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff274d9c000) libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007ff274b7f000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007ff27497b000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007ff27475c000) /lib64/ld-linux-x86-64.so.2 (0x00007ff27559f000) 

Anda dapat melihat libjli.so yang terhubung dengannya. Perpustakaan ini berisi antarmuka peluncur - satu set fungsi yang digunakan java untuk menginisialisasi dan memulai mesin virtual, di antaranya ada JLI_Launch.


Daftar lengkap fitur antarmuka
 $ objdump -T -j .text libjli.so libjli.so: file format elf64-x86-64 DYNAMIC SYMBOL TABLE: 0000000000009280 g DF .text 0000000000000038 Base JLI_List_add 0000000000003330 g DF .text 00000000000001c3 Base JLI_PreprocessArg 0000000000008180 g DF .text 0000000000000008 Base JLI_GetStdArgs 0000000000008190 g DF .text 0000000000000008 Base JLI_GetStdArgc 0000000000007e50 g DF .text 00000000000000b8 Base JLI_ReportErrorMessage 000000000000a400 g DF .text 00000000000000df Base JLI_ManifestIterate 0000000000002e70 g DF .text 0000000000000049 Base JLI_InitArgProcessing 0000000000008000 g DF .text 0000000000000011 Base JLI_ReportExceptionDescription 0000000000003500 g DF .text 0000000000000074 Base JLI_AddArgsFromEnvVar 0000000000007f10 g DF .text 00000000000000e9 Base JLI_ReportErrorMessageSys 0000000000005840 g DF .text 00000000000000b8 Base JLI_ReportMessage 0000000000009140 g DF .text 000000000000003a Base JLI_SetTraceLauncher 0000000000009020 g DF .text 000000000000000a Base JLI_MemFree 0000000000008f90 g DF .text 0000000000000026 Base JLI_MemAlloc 00000000000059c0 g DF .text 0000000000002013 Base JLI_Launch 00000000000091c0 g DF .text 000000000000003b Base JLI_List_new 0000000000008ff0 g DF .text 0000000000000026 Base JLI_StringDup 0000000000002ec0 g DF .text 000000000000000c Base JLI_GetAppArgIndex 

Setelah transfer kontrol ke JLI_Launch, sejumlah tindakan diperlukan untuk memulai JVM, seperti:


Saya Memuat karakter JVM HotSpot ke dalam memori dan mendapatkan pointer ke fungsi untuk membuat VM.


Semua kode JVM HotSpot terletak di perpustakaan libjvm.so. Setelah menentukan jalur absolut ke libjvm.so, pustaka dimuat ke memori dan penunjuk ke fungsi JNI_CreateJavaVM dicabut darinya . Pointer fungsi ini disimpan dan selanjutnya digunakan untuk membuat dan menginisialisasi mesin virtual.


Jelas libjvm.so tidak ditautkan ke libjli.so


II Argumen parsing berlalu setelah preprocessing.


Fungsi dengan nama berbicara ParseArguments mem-parsing argumen yang dilewatkan dari baris perintah. Parser argumen ini mendefinisikan mode startup aplikasi


 enum LaunchMode { // cf. sun.launcher.LauncherHelper LM_UNKNOWN = 0, LM_CLASS, LM_JAR, LM_MODULE, LM_SOURCE }; 

Itu juga mengubah bagian dari argumen ke format -DpropertyName=propertyValue , misalnya, -cp=/path dikonversi ke -Djava.class.path=/path . Selanjutnya, SystemProperty tersebut disimpan dalam array global di JVM HotSpot dan diteruskan ke java.lang.System::props pada fase pertama inisialisasi (Dalam JDK12, mekanisme inisialisasi java.lang.System.props telah dimodifikasi, lebih dalam komit ini ).


Argumen parsing juga membuang beberapa opsi yang tidak diproses oleh JVM (misalnya --list-modules , pemrosesan opsi ini terjadi langsung di launcher pada saat ini ).


III . Garpu utas primordial dan buat VM di dalamnya


Tetapi jika ada yang salah, upaya dilakukan untuk memulai JVM di utas utama "coba saja".


Setelah mempelajari pertanyaan itu, saya menemukan salah satu alasan yang memungkinkan mengapa JVM tidak dimulai di utas utama. Faktanya adalah bahwa (setidaknya di Linux) pthreads dan utas utama bekerja secara berbeda dengan stack. Ukuran main-thread'a dibatasi oleh ulimit -s , yaitu saat mengatur nilai besar yang sewenang-wenang, kami mendapatkan tumpukan besar yang sewenang-wenang. Utas utama menggunakan sesuatu yang mirip dengan MAP_GROWSDOWN , tetapi tidak MAP_GROWSDOWN . Menggunakan MAP_GROWSDOWN dalam bentuknya yang murni tidak aman dan, jika ingatanku benar, terkunci. Di komputer saya, MAP_GROWSDOWN tidak menambahkan efek apa pun. Perbedaan antara pemetaan utas utama dan MAP_GROWSDOWN adalah bahwa tidak ada mmap lain, dengan pengecualian MAP_FIXED , akan dapat bertabrakan dengan bidang kemungkinan ekspansi tumpukan. Semua yang diperlukan dari perangkat lunak adalah untuk menetapkan nilai rsp sesuai dan kemudian OS akan mengetahuinya: Dan kesalahan halaman akan memproses dan penjaga akan mengatur . Perbedaan ini memengaruhi sejumlah penggaruk: Saat menentukan ukuran tumpukan aliran saat ini , saat membuat halaman jaga


Jadi, kami akan menganggap bahwa saat ini kami telah berhasil mengurai opsi dan membuat utas untuk VM. Setelah itu, utas hanya bercabang mulai membuat mesin virtual dan memasuki fungsi Threads :: create_vm


Dalam fungsi ini, sejumlah besar dibuat ilmu hitam inisialisasi, kami akan tertarik hanya beberapa dari mereka.


Inisialisasi penerjemah dan transfer kontrol ke kode java


Untuk setiap instruksi di JVM HotSpot ada template kode mesin khusus untuk arsitektur tertentu. Ketika penerjemah mulai menjalankan instruksi, hal pertama yang dicari adalah alamat templatnya di tabel DispatchTable khusus. Selanjutnya, lompat ke alamat templat ini dan setelah eksekusi instruksi selesai, jvm mengeluarkan alamat instruksi selanjutnya secara berurutan ) dan mulai mengeksekusinya dengan cara yang sama, dan seterusnya. Perilaku ini diamati dengan interpreter hanya untuk instruksi yang tidak "mengirim", misalnya, instruksi aritmatika ( xsub , xdiv , dll, di mana x - i , l , f , d ). Yang mereka lakukan adalah melakukan operasi aritmatika.


Dalam kasus instruksi pemanggilan prosedur ( invokestatic , invokevirtual , dll.), Instruksi selanjutnya yang akan dieksekusi akan menjadi instruksi pertama dalam prosedur yang disebut. Instruksi semacam itu sendiri menuliskan alamat dari bytecode-instructions yang akan dieksekusi dalam template mereka.


Untuk memastikan pengoperasian mesin ini di Threads::create_vm , sejumlah inisialisasi dilakukan di mana penerjemah bergantung:


Saya Menginisialisasi tabel bytecodes yang tersedia


Sebelum melanjutkan dengan inisialisasi interpreter, perlu menginisialisasi tabel bytecodes yang digunakan. Ini dijalankan dalam fungsi Bytecodes :: inisialisasi dan disajikan sebagai label yang sangat mudah dibaca. Fragmennya adalah sebagai berikut:


  // Java bytecodes // bytecode bytecode name format wide f. result tp stk traps def(_nop , "nop" , "b" , NULL , T_VOID , 0, false); def(_aconst_null , "aconst_null" , "b" , NULL , T_OBJECT , 1, false); def(_iconst_m1 , "iconst_m1" , "b" , NULL , T_INT , 1, false); def(_iconst_0 , "iconst_0" , "b" , NULL , T_INT , 1, false); def(_iconst_1 , "iconst_1" , "b" , NULL , T_INT , 1, false); def(_iconst_2 , "iconst_2" , "b" , NULL , T_INT , 1, false); def(_iconst_3 , "iconst_3" , "b" , NULL , T_INT , 1, false); def(_iconst_4 , "iconst_4" , "b" , NULL , T_INT , 1, false); def(_iconst_5 , "iconst_5" , "b" , NULL , T_INT , 1, false); def(_lconst_0 , "lconst_0" , "b" , NULL , T_LONG , 2, false); def(_lconst_1 , "lconst_1" , "b" , NULL , T_LONG , 2, false); def(_fconst_0 , "fconst_0" , "b" , NULL , T_FLOAT , 1, false); def(_fconst_1 , "fconst_1" , "b" , NULL , T_FLOAT , 1, false); def(_fconst_2 , "fconst_2" , "b" , NULL , T_FLOAT , 1, false); def(_dconst_0 , "dconst_0" , "b" , NULL , T_DOUBLE , 2, false); def(_dconst_1 , "dconst_1" , "b" , NULL , T_DOUBLE , 2, false); def(_bipush , "bipush" , "bc" , NULL , T_INT , 1, false); def(_sipush , "sipush" , "bcc" , NULL , T_INT , 1, false); def(_ldc , "ldc" , "bk" , NULL , T_ILLEGAL, 1, true ); def(_ldc_w , "ldc_w" , "bkk" , NULL , T_ILLEGAL, 1, true ); def(_ldc2_w , "ldc2_w" , "bkk" , NULL , T_ILLEGAL, 2, true ); 

Sesuai dengan tabel ini, untuk setiap bytecode panjangnya diatur (ukuran selalu 1 byte, tetapi mungkin juga ada indeks di ConstantPool , serta bytecode lebar), nama, bytecode dan bendera:


 bool Bytecodes::_is_initialized = false; const char* Bytecodes::_name [Bytecodes::number_of_codes]; BasicType Bytecodes::_result_type [Bytecodes::number_of_codes]; s_char Bytecodes::_depth [Bytecodes::number_of_codes]; u_char Bytecodes::_lengths [Bytecodes::number_of_codes]; Bytecodes::Code Bytecodes::_java_code [Bytecodes::number_of_codes]; unsigned short Bytecodes::_flags [(1<<BitsPerByte)*2]; 

Parameter ini selanjutnya diperlukan untuk menghasilkan kode templat juru bahasa.


II Inisialisasi Kode Cache


Untuk menghasilkan kode untuk template juru bahasa, Anda harus terlebih dahulu mengalokasikan memori untuk bisnis ini. Reservasi memori untuk kode cache diterapkan dalam fungsi dengan nama yang sama CodeCache :: initialize () . Seperti dapat dilihat dari bagian kode berikut dari fungsi ini


  CodeCacheExpansionSize = align_up(CodeCacheExpansionSize, os::vm_page_size()); if (SegmentedCodeCache) { // Use multiple code heaps initialize_heaps(); } else { // Use a single code heap FLAG_SET_ERGO(uintx, NonNMethodCodeHeapSize, 0); FLAG_SET_ERGO(uintx, ProfiledCodeHeapSize, 0); FLAG_SET_ERGO(uintx, NonProfiledCodeHeapSize, 0); ReservedCodeSpace rs = reserve_heap_memory(ReservedCodeCacheSize); add_heap(rs, "CodeCache", CodeBlobType::All); } 

kode cache dikontrol oleh opsi -XX:ReservedCodeCacheSize , -XX:SegmentedCodeCache , -XX:CodeCacheExpansionSize , -XX:NonNMethodCodeHeapSize , -XX:ProfiledCodeHeapSize -XX:CodeCacheExpansionSize , -XX:NonNMethodCodeHeapSize . Penjelasan singkat tentang opsi-opsi ini dapat ditemukan pada tautan yang dipimpinnya. Selain baris perintah, nilai beberapa opsi ini disesuaikan secara ergonomis, misalnya, jika nilai SegmentedCodeCache secara default (tidak aktif), kemudian dengan ukuran kode >= 240Mb , SegmentedCodeCache akan dimasukkan dalam CompilerConfig :: set_tiered_flags .


Setelah melakukan pemeriksaan, area ukuran byte ReservedCodeCacheSize . Jika SegmentedCodeCache ternyata terpapar, maka area ini dibagi menjadi beberapa bagian: Metode yang dikompilasi JIT, rutin tikaman, dll.


III . Inisialisasi pola interpreter


Setelah tabel bytecode dan kode cache diinisialisasi, Anda dapat melanjutkan ke pembuatan kode templat juru bahasa. Untuk melakukan ini, juru bahasa cadangan buffer dari kode cache yang diinisialisasi sebelumnya. Pada setiap tahap pembuatan kode, kode - bagian kecil kode - akan dipotong dari buffer . Setelah menyelesaikan generasi saat ini, bagian dari codelet yang tidak digunakan oleh kode dibebaskan dan menjadi tersedia untuk generasi kode berikutnya.


Pertimbangkan masing-masing langkah ini secara individual:



  { CodeletMark cm(_masm, "slow signature handler"); AbstractInterpreter::_slow_signature_handler = generate_slow_signature_handler(); } 

penangan tanda tangan digunakan untuk menyiapkan argumen untuk panggilan ke metode asli. Dalam hal ini, penangan generik dihasilkan jika, misalnya, metode asli memiliki lebih dari 13 argumen (saya tidak memeriksa dalam debugger, tetapi menilai dengan kode itu harus seperti ini)



  { CodeletMark cm(_masm, "error exits"); _unimplemented_bytecode = generate_error_exit("unimplemented bytecode"); _illegal_bytecode_sequence = generate_error_exit("illegal bytecode sequence - method not verified"); } 

VM memvalidasi file class selama inisialisasi, tetapi ini dalam kasus argumen pada stack tidak dalam format yang diperlukan atau bytecode yang tidak diketahui oleh VM. Rintisan ini digunakan saat membuat kode templat untuk masing-masing bytecode.



Setelah memanggil prosedur, perlu untuk mengembalikan data frame stack, yang sebelum prosedur dipanggil dari mana pengembalian dilakukan.



Digunakan saat memanggil runtime dari seorang juru bahasa.


  • Melempar pengecualian


  • Metode Titik Masuk


     #define method_entry(kind) \ { CodeletMark cm(_masm, "method entry point (kind = " #kind ")"); \ Interpreter::_entry_table[Interpreter::kind] = generate_method_entry(Interpreter::kind); \ Interpreter::update_cds_entry_table(Interpreter::kind); \ } 

    Disajikan sebagai makro tergantung pada jenis metode. Dalam kasus umum, persiapan frame stack yang ditafsirkan dilakukan, cek StackOverflow, stack-banging Untuk metode asli, penangan tanda tangan didefinisikan.


  • Pembuatan Bytecode Template



  // Bytecodes set_entry_points_for_all_bytes(); // installation of code in other places in the runtime // (ExcutableCodeManager calls not needed to copy the entries) set_safepoints_for_all_bytes(); 

Untuk menjalankan instruksi, spesifikasi VM mengharuskan operan berada di Operand Stack , tetapi ini tidak mencegah HotSpot dari caching mereka dalam register. Untuk menentukan keadaan saat ini dari bagian atas tumpukan, enumerasi digunakan .


 enum TosState { // describes the tos cache contents btos = 0, // byte, bool tos cached ztos = 1, // byte, bool tos cached ctos = 2, // char tos cached stos = 3, // short tos cached itos = 4, // int tos cached ltos = 5, // long tos cached ftos = 6, // float tos cached dtos = 7, // double tos cached atos = 8, // object cached vtos = 9, // tos not cached number_of_states, ilgl // illegal state: should not occur }; 

Setiap instruksi menentukan status input dan output dari bagian atas TosState dari stack, dan pembentukan pola terjadi tergantung pada status ini. Templat ini diinisialisasi dalam tabel templat yang dapat dibaca. Sebuah fragmen dari tabel ini adalah sebagai berikut:


 // interpr. templates // Java spec bytecodes ubcp|disp|clvm|iswd in out generator argument def(Bytecodes::_nop , ____|____|____|____, vtos, vtos, nop , _ ); def(Bytecodes::_aconst_null , ____|____|____|____, vtos, atos, aconst_null , _ ); def(Bytecodes::_iconst_m1 , ____|____|____|____, vtos, itos, iconst , -1 ); def(Bytecodes::_iconst_0 , ____|____|____|____, vtos, itos, iconst , 0 ); def(Bytecodes::_iconst_1 , ____|____|____|____, vtos, itos, iconst , 1 ); def(Bytecodes::_iconst_2 , ____|____|____|____, vtos, itos, iconst , 2 ); def(Bytecodes::_iconst_3 , ____|____|____|____, vtos, itos, iconst , 3 ); def(Bytecodes::_iconst_4 , ____|____|____|____, vtos, itos, iconst , 4 ); def(Bytecodes::_iconst_5 , ____|____|____|____, vtos, itos, iconst , 5 ); def(Bytecodes::_lconst_0 , ____|____|____|____, vtos, ltos, lconst , 0 ); def(Bytecodes::_lconst_1 , ____|____|____|____, vtos, ltos, lconst , 1 ); def(Bytecodes::_fconst_0 , ____|____|____|____, vtos, ftos, fconst , 0 ); def(Bytecodes::_fconst_1 , ____|____|____|____, vtos, ftos, fconst , 1 ); def(Bytecodes::_fconst_2 , ____|____|____|____, vtos, ftos, fconst , 2 ); def(Bytecodes::_dconst_0 , ____|____|____|____, vtos, dtos, dconst , 0 ); def(Bytecodes::_dconst_1 , ____|____|____|____, vtos, dtos, dconst , 1 ); def(Bytecodes::_bipush , ubcp|____|____|____, vtos, itos, bipush , _ ); def(Bytecodes::_sipush , ubcp|____|____|____, vtos, itos, sipush , _ ); 

Kami akan sangat tertarik in , out dan generator .


in - keadaan bagian atas tumpukan pada saat instruksi dimulai
out - keadaan bagian atas tumpukan pada saat selesai
generator - generator template kode instruksi mesin


Tampilan umum templat untuk semua bytecode dapat digambarkan sebagai:


  1. Jika bit pengiriman tidak diatur untuk instruksi, prolog instruksi dieksekusi (no-op pada x86)


  2. Menggunakan generator , kode mesin dihasilkan


  3. Jika bit pengiriman tidak diatur untuk instruksi, transisi ke instruksi berikutnya dilakukan tergantung pada keadaan out dari bagian atas tumpukan, yang akan in untuk instruksi berikutnya



Alamat titik entri untuk templat yang dihasilkan disimpan di tabel global dan dapat digunakan untuk debugging.


Di HotSpot, kode yang relatif bodoh berikut ini bertanggung jawab untuk ini:


Generator kode instruksi
 void TemplateInterpreterGenerator::set_entry_points(Bytecodes::Code code) { CodeletMark cm(_masm, Bytecodes::name(code), code); // initialize entry points assert(_unimplemented_bytecode != NULL, "should have been generated before"); assert(_illegal_bytecode_sequence != NULL, "should have been generated before"); address bep = _illegal_bytecode_sequence; address zep = _illegal_bytecode_sequence; address cep = _illegal_bytecode_sequence; address sep = _illegal_bytecode_sequence; address aep = _illegal_bytecode_sequence; address iep = _illegal_bytecode_sequence; address lep = _illegal_bytecode_sequence; address fep = _illegal_bytecode_sequence; address dep = _illegal_bytecode_sequence; address vep = _unimplemented_bytecode; address wep = _unimplemented_bytecode; // code for short & wide version of bytecode if (Bytecodes::is_defined(code)) { Template* t = TemplateTable::template_for(code); assert(t->is_valid(), "just checking"); set_short_entry_points(t, bep, cep, sep, aep, iep, lep, fep, dep, vep); } if (Bytecodes::wide_is_defined(code)) { Template* t = TemplateTable::template_for_wide(code); assert(t->is_valid(), "just checking"); set_wide_entry_point(t, wep); } // set entry points EntryPoint entry(bep, zep, cep, sep, aep, iep, lep, fep, dep, vep); Interpreter::_normal_table.set_entry(code, entry); Interpreter::_wentry_point[code] = wep; } //... void TemplateInterpreterGenerator::set_short_entry_points(Template* t, address& bep, address& cep, address& sep, address& aep, address& iep, address& lep, address& fep, address& dep, address& vep) { assert(t->is_valid(), "template must exist"); switch (t->tos_in()) { case btos: case ztos: case ctos: case stos: ShouldNotReachHere(); // btos/ctos/stos should use itos. break; case atos: vep = __ pc(); __ pop(atos); aep = __ pc(); generate_and_dispatch(t); break; case itos: vep = __ pc(); __ pop(itos); iep = __ pc(); generate_and_dispatch(t); break; case ltos: vep = __ pc(); __ pop(ltos); lep = __ pc(); generate_and_dispatch(t); break; case ftos: vep = __ pc(); __ pop(ftos); fep = __ pc(); generate_and_dispatch(t); break; case dtos: vep = __ pc(); __ pop(dtos); dep = __ pc(); generate_and_dispatch(t); break; case vtos: set_vtos_entry_points(t, bep, cep, sep, aep, iep, lep, fep, dep, vep); break; default : ShouldNotReachHere(); break; } } //... void TemplateInterpreterGenerator::generate_and_dispatch(Template* t, TosState tos_out) { if (PrintBytecodeHistogram) histogram_bytecode(t); #ifndef PRODUCT // debugging code if (CountBytecodes || TraceBytecodes || StopInterpreterAt > 0) count_bytecode(); if (PrintBytecodePairHistogram) histogram_bytecode_pair(t); if (TraceBytecodes) trace_bytecode(t); if (StopInterpreterAt > 0) stop_interpreter_at(); __ verify_FPU(1, t->tos_in()); #endif // !PRODUCT int step = 0; if (!t->does_dispatch()) { step = t->is_wide() ? Bytecodes::wide_length_for(t->bytecode()) : Bytecodes::length_for(t->bytecode()); if (tos_out == ilgl) tos_out = t->tos_out(); // compute bytecode size assert(step > 0, "just checkin'"); // setup stuff for dispatching next bytecode if (ProfileInterpreter && VerifyDataPointer && MethodData::bytecode_has_profile(t->bytecode())) { __ verify_method_data_pointer(); } __ dispatch_prolog(tos_out, step); } // generate template t->generate(_masm); // advance if (t->does_dispatch()) { #ifdef ASSERT // make sure execution doesn't go beyond this point if code is broken __ should_not_reach_here(); #endif // ASSERT } else { // dispatch to next bytecode __ dispatch_epilog(tos_out, step); } } 

, . JVM. Java- . JavaCalls . JVM , main .



, , :


 public class Sum{ public static void sum(int a, int b){ return a + b; } } public class Main { public static void main(String args[]){ Sum.sum(2, 3); } } 

Sum.sum(II) .


2 javac -c *.java , .
Sum.sum :


  descriptor: (II)I flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=2 0: iload_0 1: iload_1 2: iadd 3: ireturn LineNumberTable: line 3: 0 

Main.main


  descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: iconst_2 1: iconst_3 2: invokestatic #2 // Method Sum.sum:(II)I 5: pop 6: return LineNumberTable: line 13: 0 line 14: 6 

, — .


invokestatic ' x86 - HotSpot


 void TemplateTable::invokestatic(int byte_no) { transition(vtos, vtos); assert(byte_no == f1_byte, "use this argument"); prepare_invoke(byte_no, rbx); // get f1 Method* // do the call __ profile_call(rax); __ profile_arguments_type(rax, rbx, rbcp, false); __ jump_from_interpreted(rbx, rax); } 

byte_no == f1_byteConstantPoolCache , , rbx — , Method * . : , , ( method_entry ).


prepare_invoke . , invokestatic ConstantPool Constant_Methodref_Info . HotSpot . 2 .. ConstantPoolCache . ConstantPoolCache , (, ConstantPoolCacheEntry , ). ConstantPoolCacheEntry , ( 0) / . , ConstantPool , ConstantPoolCache ( x86 Little Endian).


, , HotSpot prepare_invokeConstantPoolCache . , , ConstantPoolCacheEntry


  __ get_cache_and_index_and_bytecode_at_bcp(Rcache, index, temp, byte_no, 1, index_size); __ cmpl(temp, code); // have we resolved this bytecode? __ jcc(Assembler::equal, resolved); // resolve first time through address entry = CAST_FROM_FN_PTR(address, InterpreterRuntime::resolve_from_cache); __ movl(temp, code); __ call_VM(noreg, entry, temp); // Update registers with resolved info __ get_cache_and_index_at_bcp(Rcache, index, 1, index_size); __ bind(resolved); 

, InterpreterRuntime::resolve_from_cache .


receiver'a , . (, , , ConstantPoolCache <clinit> , ). define class, EagerInitialization ( , , :)). HotSpot ( CDS ) .


, , ConstantPoolCacheEntry . Method * rbx , , .


Sum.sum(2, 3) . gdb-script sum.gdb :


 #    java file /home/dmitrii/jdk12/build/linux-x86_64-server-fastdebug/images/jdk/bin/java # gdb    SEGV' #,   https://hg.openjdk.java.net/jdk/jdk12/file/06222165c35f/src/hotspot/cpu/x86/vm_version_x86.cpp#l361 handle SIGSEGV nostop noprint #       set breakpoint pending on #    , #    set pagination off #      main b PostJVMInit commands #   , #    set $buffer = malloc(1000) #   . #jmp       # invokestatic b *AbstractInterpreter::_entry_table[0] thread 2 commands #     invokestatic, # Method*   rbx set $mthd = (Method *) $rbx #    $buffer call $mthd->name_and_sig_as_C_string($buffer, 1000) if strcmp()($buffer, "Sum.sum(II)I") == 0 #  iload_0,     b *TemplateInterpreter::_normal_table._table[vtos][26] thread 2 #  iload_1,   - int,  #  iload_0 b *TemplateInterpreter::_normal_table._table[itos][27] thread 2 #   iadd b *TemplateInterpreter::_normal_table._table[itos][96] thread 2 end c end c end r -cp . Main 

gdb -x sum.gdb , Sum.sum


 $453 = 0x7ffff7fdcdd0 "Sum.sum(II)I" 

layout asm , , generate_normal_entry . -, StackOverflow, stack-banging dispatch iload_0 . :


 0x7fffd828fa1f mov eax,DWORD PTR [r14] ;, iload_0 0x7fffd828fa22 movzx ebx,BYTE PTR [r13+0x1] ;   0x7fffd828fa27 inc r13 ; bcp (byte code pointer) 0x7fffd828fa2a movabs r10,0x7ffff717e8a0 ; DispatchTable 0x7fffd828fa34 jmp QWORD PTR [r10+rbx*8] ;jump      

rax ,


 0x7fffd828fabe push rax ;     ;   ,      0x7fffd828fabf mov eax,DWORD PTR [r14-0x8] 0x7fffd828fac3 movzx ebx,BYTE PTR [r13+0x1] 0x7fffd828fac8 inc r13 0x7fffd828facb movabs r10,0x7ffff717e8a0 0x7fffd828fad5 jmp QWORD PTR [r10+rbx*8] 

iadd :


 0x7fffd8292ba7 mov edx,DWORD PTR [rsp] ; ,     iload_1 0x7fffd8292baa add rsp,0x8 ; rsp    0x7fffd8292bae add eax,edx ;   0x7fffd8292bb0 movzx ebx,BYTE PTR [r13+0x1] 0x7fffd8292bb5 inc r13 0x7fffd8292bb8 movabs r10,0x7ffff717e8a0 0x7fffd8292bc2 jmp QWORD PTR [r10+rbx*8] 

gdb eax edx ,


 (gdb) p $eax $457 = 3 (gdb) p $edx $458 = 2 

, Sum.sum .

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


All Articles