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 {
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:
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) {
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
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 {
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:
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:
Jika bit pengiriman tidak diatur untuk instruksi, prolog instruksi dieksekusi (no-op pada x86)
Menggunakan generator
, kode mesin dihasilkan
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);
, . 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);
byte_no == f1_byte
— ConstantPoolCache
, , 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_invoke
— ConstantPoolCache
. , , ConstantPoolCacheEntry
__ get_cache_and_index_and_bytecode_at_bcp(Rcache, index, temp, byte_no, 1, index_size); __ cmpl(temp, code);
, 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
.