يعرف كل مطور برامج Java تقريبًا أن البرامج المكتوبة بلغة Java يتم تجميعها مبدئيًا في رمز JVM وتخزينها في صورة ملفات فئة بتنسيق قياسي . بعد الحصول على ملفات الفئة هذه داخل الجهاز الظاهري وحتى يقوم المترجم بالوصول إليها ، يفسر JVM الرمز الفرعي الوارد في ملفات الفئة هذه. توفر هذه المقالة نظرة عامة حول كيفية عمل المترجم الشفهي فيما يتعلق بـ OpenJDK JVM HotSpot.
محتوى المقال:
- البيئة
- تشغيل تطبيق جافا
- تهيئة المترجم والتحكم في النقل إلى كود جافا
- مثال
البيئة
للتجارب ، نستخدم مجموعة أحدث إصدارات OpenJDK JDK12 المتاحة مع التكوين التلقائي
--enable-debug --with-native-debug-symbols=internal
على Ubuntu 18.04 / gcc 7.4.0.
--with-native-debug-symbols=internal
يعني أنه عند بناء JDK ، سيتم تضمين رموز debazh في الثنائيات نفسها.
--enable-debug
- أن الثنائي سيحتوي على رمز تصحيح إضافي.
بناء JDK 12 في مثل هذه البيئة ليست عملية معقدة. كل ما كنت بحاجة إلى القيام به هو تثبيت JDK11 ( JDK n-1 مطلوب لإنشاء JDK n ) وتسليم المكتبات اللازمة تلقائيًا للإشارة التلقائية. بعد ذلك ، قم بتشغيل الأمر
bash configure --enable-debug --with-native-debug-symbols=internal && make CONF=fastdebug images
وبعد الانتظار قليلاً (على جهاز الكمبيوتر المحمول الخاص بي حوالي 10 دقائق) ، نحصل على fastdebug build JDK 12.
من حيث المبدأ ، سيكون من السهل جدًا تثبيت jdk من المستودعات العامة وتزويد حزمة openjdk-xx-dbg بالإضافة إلى ذلك برموز التصحيح ، حيث أن xx هي إصدار jdk ، ولكن التجميع fastdebug يوفر وظائف للتصحيح من gdb ، والتي يمكن أن تجعل الحياة أسهل في بعض الحالات. في الوقت الحالي ، أستخدم بنشاط ps () ، وهي وظيفة لعرض آثار مكدس Java من gdb ، و pfl () ، وهي وظيفة لتحليل مكدس الإطارات (مفيد جدًا عند تصحيح أخطاء المترجم الفوري في gdb).
مثال ps () و pfl ()على سبيل المثال ، ضع في الاعتبار برنامج نصي gdb التالي
# 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
نتيجة تشغيل مثل هذا البرنامج النصي هي:
"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
كما ترون ، في حالة ps()
نحصل فقط على مكدس الاستدعاءات ، في حالة pfl()
- المنظمة الكاملة للمكدس.
تشغيل تطبيق جافا
قبل متابعة مناقشة المترجم الفوري مباشرةً ، سنراجع بإيجاز الإجراءات التي يتم تنفيذها قبل نقل التحكم إلى شفرة java. على سبيل المثال ، خذ برنامج جافا "لا يفعل شيئًا على الإطلاق":
public class Main { public static void main(String args[]){ } }
وحاول معرفة ما يحدث عند تشغيل مثل هذا التطبيق:
javac Main.java && java Main
أول ما يجب فعله للإجابة على هذا السؤال هو العثور على java binary وإلقاء نظرة عليه - وهو الذي نستخدمه لتشغيل جميع تطبيقات JVM الخاصة بنا. في حالتي ، وهي تقع على طول الطريق
/home/dmitrii/jdk12/build/linux-x86_64-server-fastdebug/images/jdk/bin/java
.
لكن في النهاية ، لا يوجد شيء مميز يمكن مشاهدته. هذا ثنائي والذي ، إلى جانب رموز debazhnymi ، لا يستغرق سوى 20 كيلو بايت ويتم تجميعه من قاذفة / مصدر ملف مصدر واحد فقط.
كل ما يفعله هو تلقي وسيطات سطر الأوامر (char * argv []) ، وقراءة الوسائط من متغير البيئة JDK_JAVA_OPTIONS ، وإجراء المعالجة المسبقة الأساسية والتحقق من الصحة (على سبيل المثال ، لا يمكنك إضافة خيار المحطة الطرفية أو اسم الفئة الرئيسية إلى متغير البيئة هذا) واستدعاء الوظيفة JLI_La مع قائمة الوسيطة الناتجة.
لا يتم تضمين تعريف وظيفة JLI_Launch في java binary ، وإذا نظرت إلى التبعيات المباشرة:
$ 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)
تستطيع أن ترى libjli.so الذي يرتبط به. تحتوي هذه المكتبة على واجهة قاذفة - مجموعة من الوظائف التي تستخدم java لتهيئة وبدء تشغيل جهاز ظاهري ، من بينها JLI_Launch.
قائمة كاملة من ميزات واجهة $ 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
بعد نقل التحكم إلى JLI_Launch ، يلزم اتخاذ عدد من الإجراءات لبدء JVM ، مثل:
I. تحميل أحرف JVM HotSpot في الذاكرة والحصول على مؤشر إلى وظيفة لإنشاء VM.
كل كود JVM HotSpot موجود في مكتبة libjvm.so. بعد تحديد المسار المطلق إلى libjvm.so ، يتم تحميل المكتبة في الذاكرة وتمزيق المؤشر إلى دالة JNI_CreateJavaVM . يتم تخزين مؤشر الوظيفة هذا واستخدامه فيما بعد لإنشاء الجهاز الظاهري وتهيئته.
من الواضح أن libjvm.so غير مرتبط بـ libjli.so
الثاني . تحليل الحجج التي تم تمريرها بعد المعالجة المسبقة.
تقوم دالة تحمل الاسم الناطق ParseArguments بتوزيع الوسائط التي تم تمريرها من سطر الأوامر. يعرّف محلل الوسيطة وضع بدء تشغيل التطبيق
enum LaunchMode {
كما أنه يحول جزءًا من الوسائط إلى التنسيق -DpropertyName=propertyValue
، على سبيل المثال ، -cp=/path
تحويل -cp=/path
إلى -Djava.class.path=/path
. علاوة على ذلك ، يتم تخزين SystemProperty
في الصفيف العمومي في JVM HotSpot وإعادة توجيهه إلى java.lang.System::props
في المرحلة الأولى من التهيئة (في JDK12 ، تم تعديل آلية التهيئة لـ java.lang.System.props ، أكثر في هذا الالتزام ).
تحليل الحجج يتجاهل أيضًا بعض الخيارات التي لم تتم معالجتها بواسطة JVM (على سبيل المثال --list-modules
، تحدث معالجة هذا الخيار مباشرة في المشغّل في هذه المرحلة ).
ثالثا . شوكة موضوع بدائية وإنشاء VM فيه
ولكن إذا حدث خطأ ما ، يتم إجراء محاولة لبدء تشغيل JVM في الخيط الرئيسي "فقط قم بتجربته".
بعد دراسة السؤال ، وجدت أحد الأسباب المحتملة لعدم بدء JVM في سلسلة الرسائل الرئيسية. والحقيقة هي أن (على الأقل على لينكس) pthreads والخيط الرئيسي العمل بشكل مختلف مع المكدس. يقتصر حجم thread- الرئيسي بواسطة ulimit ulimit -s
، أي عند تحديد قيمة كبيرة بشكل تعسفي ، نحصل على كومة كبيرة بشكل تعسفي. يستخدم مؤشر الترابط الرئيسي شيئًا مشابهًا لـ MAP_GROWSDOWN ، ولكن لا MAP_GROWSDOWN
. استخدام MAP_GROWSDOWN
في شكله النقي ليس آمنًا ، وإذا كانت الذاكرة MAP_GROWSDOWN
صحيح ، تكون مؤمنة. على الجهاز الخاص بي ، لا يضيف MAP_GROWSDOWN
أي تأثير. يتمثل الاختلاف بين تعيين مؤشر الترابط الرئيسي و MAP_GROWSDOWN في أنه لا يوجد ملف mmap
، باستثناء MAP_FIXED
، سيكون قادراً على التصادم مع منطقة توسيع المكدس المحتملة. كل ما نحتاجه من البرنامج هو تعيين قيمة rsp
المقابلة ومن ثم سيقوم نظام التشغيل rsp
: وسيعمل خطأ الصفحة rsp
الحارس . يؤثر هذا الاختلاف على عدد من المكابس: عند تحديد حجم مكدس الدفق الحالي ، عند إنشاء صفحات الحماية
لذلك ، سوف نفترض أنه في الوقت الحالي قمنا بتحليل الخيارات بنجاح وقمنا بإنشاء مؤشر ترابط لـ VM. بعد ذلك ، يبدأ مؤشر الترابط forked فقط في إنشاء جهاز افتراضي وإدخال دالة Threads :: create_vm
في هذه الوظيفة ، يتم إجراء عدد كبير إلى حد ما السحر الاسود التهيئة ، سنكون مهتمين فقط عدد قليل منهم.
تهيئة المترجم ونقل السيطرة على كود جافا
لكل تعليمات في JVM HotSpot ، يوجد قالب رمز آلة معين لهيكل معين. عندما يبدأ المترجم في تنفيذ التعليمات ، فإن أول شيء يبحث عنه هو عنوان القالب الخاص به في جدول DispatchTable الخاص. بعد ذلك ، انتقل إلى عنوان هذا القالب وبعد اكتمال تنفيذ التعليمات ، تقوم jvm بإخراج عنوان التعليمة التالية بالترتيب ) وتبدأ في تنفيذها بالطريقة نفسها ، وهكذا. يتم ملاحظة هذا السلوك مع المترجم فقط للإرشادات التي لا "ترسل" ، على سبيل المثال ، الإرشادات الحسابية ( xsub
، xdiv
، إلخ ، حيث x
- i
، l
، f
، d
). كل ما يفعلونه هو إجراء العمليات الحسابية.
في حالة تعليمات الاحتجاج بالإجراءات ( invokestatic
، invokevirtual
، إلخ) ، فإن التعليمة التالية التي سيتم تنفيذها ستكون هي التعليمة الأولى في الإجراء المدعو. تضع هذه التعليمات نفسها عنوان التعليمة البرمجية التالية التي سيتم تنفيذها في قالبها.
لضمان تشغيل هذا الجهاز في Threads::create_vm
، يتم تنفيذ عدد من عمليات التهيئة التي يعتمد عليها المترجم:
I. تهيئة جدول الرموز المتاحة
قبل الشروع في تهيئة المترجم الشفهي ، من الضروري تهيئة جدول الشفرات المستخدمة. يتم تنفيذه في الدالة Bytecodes :: initialize ويتم تقديمه كتسمية قابلة للقراءة للغاية. شظتها هي كما يلي:
وفقًا لهذا الجدول ، يتم تعيين طوله لكل رمز ثانوي (يكون الحجم دائمًا بايت واحد ، ولكن قد يكون هناك أيضًا فهرس في ConstantPool
، وكذلك الرموز الجانبية الواسعة) والاسم والرمز الثانوي والأعلام:
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];
هناك حاجة إلى هذه المعلمات أيضًا لإنشاء رمز قالب مترجم.
الثاني . تهيئة رمز ذاكرة التخزين المؤقت
لإنشاء رمز لقوالب المترجمين الفوريين ، يجب عليك أولاً تخصيص ذاكرة لهذا العمل. يتم تطبيق حجز الذاكرة لرمز ذاكرة التخزين المؤقت في وظيفة تحمل نفس الاسم CodeCache :: initialize () . كما يتبين من قسم الكود التالي لهذه الوظيفة
CodeCacheExpansionSize = align_up(CodeCacheExpansionSize, os::vm_page_size()); if (SegmentedCodeCache) {
يتم التحكم رمز التخزين المؤقت بواسطة الخيارات -XX:ReservedCodeCacheSize
، -XX:SegmentedCodeCache
، -XX:CodeCacheExpansionSize
، -XX:NonNMethodCodeHeapSize
، -XX:ProfiledCodeHeapSize
-XX:NonProfiledCodeHeapSize
. يمكن العثور على وصف موجز لهذه الخيارات على الروابط التي تؤدي إليها. بالإضافة إلى سطر الأوامر ، يتم ضبط قيم بعض هذه الخيارات بشكل مريح ، على سبيل المثال ، إذا تم SegmentedCodeCache
قيمة SegmentedCodeCache
افتراضيًا (إيقاف) ، ثم مع حجم الرمز >= 240Mb
، سيتم تضمين SegmentedCodeCache
في CompilerConfig :: set_tiered_flags .
بعد إجراء عمليات التحقق ، يتم حجز مساحة بحجم ReservedCodeCacheSize
بايت. إذا تبين أن SegmentedCodeCache
مكشوف ، فسيتم تقسيم هذه المنطقة إلى أجزاء: الأساليب المترجمة من JIT ، إجراءات الرعنة ، إلخ.
ثالثا . تهيئة أنماط المترجم الفوري
بعد تهيئة جدول bytecode ورمز ذاكرة التخزين المؤقت ، يمكنك المتابعة إلى إنشاء التعليمات البرمجية لقوالب المترجم. للقيام بذلك ، يحجز المترجم مخزن مؤقت من رمز ذاكرة التخزين المؤقت الذي تمت تهيئته مسبقًا. في كل مرحلة من مراحل إنشاء الكود ، سيتم قطع الكودلات - المقاطع الصغيرة من الكود - من المخزن المؤقت . بعد الانتهاء من الجيل الحالي ، يتم تحرير جزء الكودلت الذي لا يستخدمه الكود ويصبح متاحًا لأجيال الكود اللاحقة.
ضع في اعتبارك كل خطوة من هذه الخطوات بشكل فردي:
{ CodeletMark cm(_masm, "slow signature handler"); AbstractInterpreter::_slow_signature_handler = generate_slow_signature_handler(); }
يتم استخدام معالج التوقيع لإعداد الوسائط للمكالمات إلى الأساليب الأصلية. في هذه الحالة ، يتم إنشاء معالج عام إذا كان الأسلوب الأصلي ، على سبيل المثال ، يحتوي على أكثر من 13 وسيطة (لم أقم بفحصها في مصحح الأخطاء ، لكن إذا حكمنا من خلال الكود ، يجب أن يكون الأمر هكذا)
{ 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 بالتحقق من صحة classfiles أثناء التهيئة ، ولكن هذا في حالة أن الوسائط الموجودة في بنية تخزين العناصر ليست بالتنسيق المطلوب أو الرمز الفرعي الذي لا يعرفه VM. يتم استخدام هذه الرهانات عند إنشاء رمز القالب لكل من الرموز الثانوية.
بعد استدعاء الإجراءات ، من الضروري استعادة بيانات رصة الإطار ، والتي كانت قبل استدعاء الإجراء الذي تم إجراء الإرجاع منه.
تستخدم عند استدعاء وقت التشغيل من مترجم.
استثناءات رمي
طريقة إدخال النقاط
#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); \ }
قدم على شكل ماكرو اعتمادا على نوع الطريقة. في الحالة العامة ، يتم تنفيذ إعداد إطار مكدس تفسير ، والتحقق StackOverflow ، ضجيج المكدس. للأساليب الأصلية ، يتم تعريف معالج التوقيع.
Bytecode قالب الجيل
لتنفيذ التعليمات ، تتطلب مواصفات VM أن تكون المعامِلات في حزمة المعامل ، لكن هذا لا يمنع HotSpot من تخزينها مؤقتًا في السجل. لتحديد الحالة الحالية لأعلى المكدس ، استخدم التعداد
enum TosState {
يعرّف كل تعليمي حالات المدخلات والمخرجات في TosState
العلوي من المكدس TosState
، ويحدث إنشاء الأنماط وفقًا لهذه الحالة. تتم تهيئة هذه القوالب في جدول قالب قابل للقراءة. جزء من هذا الجدول كما يلي:
سنهتم بشكل خاص بالأعمدة generator
والخارجة والمولدة.
in
- حالة الجزء العلوي من المكدس في وقت بدء التعليمات
out
- حالة الجزء العلوي من المكدس في وقت الانتهاء من التعليمات
generator
- مولد رمز التعليمات البرمجية الآلة
يمكن وصف طريقة العرض العامة للقالب لجميع الرموز الثانوية على النحو التالي:
إذا لم يتم تعيين بت إرسال للإرشادات ، يتم تنفيذ prolog التعليمات (no-op على x86)
باستخدام generator
، يتم إنشاء رمز الجهاز
إذا لم يتم تعيين بت إرسال للإرشادات ، يتم تنفيذ الانتقال إلى التعليمات التالية بالترتيب على حالة out
من الجزء العلوي من المكدس ، والتي ستكون in
التعليمات التالية
يتم تخزين عنوان نقطة الإدخال للقالب الناتج في الجدول العمومي ويمكن استخدامه لتصحيح الأخطاء.
في HotSpot ، الجزء التالي من التعليمات البرمجية الغبي نسبياً هو المسؤول عن هذا:
مولد رمز التعليمات 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
.