x86下JVM HotSpot中字节码解释器的初始化和操作

几乎每个Java开发人员都知道用Java语言编写的程序最初会被编译为JVM字节码并存储为标准格式的类文件。 在虚拟机中获取此类类文件之后,直到编译器尚未访问它们为止,JVM会解释这些类文件中包含的字节码。 本文概述了有关OpenJDK JVM HotSpot的解释器如何工作。


文章内容:


  • 环境
  • 运行Java应用程序
  • 解释器初始化和控制权转移到Java代码
  • 例子

环境


对于实验,我们使用带有autoconf配置的最新可用 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,需要JDK n-1 ),然后手动交付自动配置的必需库。 接下来,运行命令


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

然后稍等片刻(在我的笔记本电脑上大约10分钟),我们得到了fastdebug构建JDK 12。


原则上,仅从公共存储库安装jdk并附带提供带有调试符号的openjdk-xx-dbg软件包就足够了,其中xx是jdk版本,但是fastdebug程序集提供了来自gdb的调试功能,在某些情况下可以简化工作。 目前,我正在积极使用ps()(用于查看来自gdb的Java堆栈跟踪信息的功能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()的情况下, pfl()完整堆栈组织。


运行Java应用程序


在直接进行解释器讨论之前,我们将简要回顾一下将控制权转移到Java代码之前执行的操作。 例如,以一个“什么都不做”的Java程序为例:


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

并尝试找出运行此类应用程序时发生的情况:


javac Main.java && java Main


回答此问题的第一件事是查找并查看Java二进制文件-我们用于运行所有JVM应用程序的二进制文件。 就我而言,它位于路径上


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


但是最后,没有什么特别值得关注的。 这是一个二进制文件,与debazhnymi符号一起仅占用20KB,并且仅从一个源文件启动器/ main.c编译


他所做的只是接收命令行参数(char * argv []), 从JDK_JAVA_OPTIONS环境变量中读取参数 ,进行基本的预处理和验证(例如,您不能在此环境变量中添加终端选项或Main class名称)并调用该函数。 JLI_Launch及其结果参数列表。


JLI_Launch函数的定义未包含在Java二进制文件中,如果您查看其直接依赖项,则该定义:


 $ 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,例如:


将JVM HotSpot字符加载到内存中,并获得指向创建VM的函数的指针。


所有JVM HotSpot代码都位于libjvm.so库中。 在确定libjvm.so的绝对路径之后,该库将加载到内存中 ,并且指向JNI_CreateJavaVM函数指针也将被删除 。 该函数指针被存储,随后用于创建和初始化虚拟机。


显然libjvm.so没有链接到libjli.so


。 解析经过预处理后传递的参数。


具有语音名称ParseArguments的函数将解析从命令行传递的参数。 此参数解析器定义应用程序启动模式


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

还将部分参数转换为-DpropertyName=propertyValue格式,例如, -cp=/path转换为-Djava.class.path=/path 。 此外,此类SystemProperty存储在JVM HotSpot的全局数组中,并在初始化第一阶段转发给java.lang.System::props (在JDK12中,已经修改了java.lang.System.props的初始化机制,在此commit中有更多修改)。


解析参数还会丢弃JVM未处理的一些选项(例如--list-modules ,此时此选项的处理直接在启动器中进行)。


。 分叉原始线程并在其中创建VM


但是,如果出现问题,则尝试在主线程中 “仅尝试一下” 启动JVM


研究了这个问题之后,我发现了JVM无法在主线程中启动的可能原因之一。 事实是(至少在Linux上)pthread和主线程在堆栈中的工作方式不同。 主线程'a的大小受ulimit -s限制,即 设置任意大的值时,我们得到一个任意大的堆栈。 主线程使用类似于MAP_GROWSDOWN的东西,但不使用MAP_GROWSDOWN 。 以纯格式使用MAP_GROWSDOWN是不安全的,并且如果内存对我MAP_GROWSDOWNMAP_GROWSDOWN其锁定。 在我的机器上, MAP_GROWSDOWN不会添加任何效果。 主线程映射和MAP_GROWSDOWN之间的区别在于,除了MAP_FIXED之外,没有其他mmap可以与可能的堆栈扩展区域产生冲突。 该软件所需要做的就是设置相应的rsp值,然后操作系统将rsp值:然后将处理页面错误,并设置保护措施 。 这种差异会影响许多耙: 在确定当前流的堆栈大小时在创建保护页面时


因此,我们将假设目前我们已经成功解析了选项并为VM创建了线程。 之后,刚刚分叉的线程开始创建虚拟机,然后进入Threads :: create_vm函数


在此功能中,产生了大量 黑魔法 初始化,我们只对其中一些感兴趣。


初始化解释器并将控制权转移到Java代码


对于JVM HotSpot中的每条指令,都有用于特定体系结构的特定机器代码模板。 当解释器开始执行指令时,它首先寻找的是其模板在特殊DispatchTable表中的地址。 接下来, 跳转到该模板的地址,并在指令执行完成后,jvm 按顺序取出下一条指令的地址,并以相同的方式开始执行,依此类推。 使用解释器只能针对不“分派”的指令(例如,算术指令( xsubxdiv等,其中x - ilfd ))观察到此行为。 他们所做的只是执行算术运算。


对于过程调用指令( invokestaticinvokestatic等),要执行的下一条指令将是被调用过程中的第一条指令。 这些指令本身将下一个要在其模板中执行的字节码指令的地址放下。


为了确保本机在Threads::create_vm ,将执行许多解释程序依赖的初始化:


初始化可用字节码表


在进行解释器的初始化之前,有必要初始化使用的字节码表。 它在Bytecodes :: initialize函数中执行,并显示为易于阅读的标签。 其片段如下:


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

根据该表,为每个字节码设置其长度(大小始终为1个字节,但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) { // 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); } 

缓存代码由以下选项控制-XX:ReservedCodeCacheSize-XX:SegmentedCodeCache-XX:CodeCacheExpansionSize-XX:NonNMethodCodeHeapSize-XX:ProfiledCodeHeapSize-XX:NonProfiledCodeHeapSize 。 这些选项的简要说明可以在它们所指向的链接中找到。 除了命令行以外,其中某些选项的值也经过了人体工程学调整,例如,如果默认情况下(关闭) SegmentedCodeCache值(关闭),则代码大小>= 240MbSegmentedCodeCache将包含在CompilerConfig :: set_tiered_flags中


执行检查后,将ReservedCodeCacheSize一个大小为ReservedCodeCacheSize bytes的区域。 如果发现SegmentedCodeCache是公开的,则此区域分为以下部分:JIT编译的方法,stab例程等。


。 解释器模式的初始化


字节码表和缓存代码初始化之后,可以继续进行解释器模板的代码生成。 为此,解释器会保留先前初始化的缓存代码中的缓冲区。 在代码生成的每个阶段,将从缓冲区中剪切出小代码(一小段代码)。 在完成当前的生成之后,小码中未被代码使用的部分将被释放,并且可用于后续代码生成。


分别考虑以下每个步骤:



  { 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会在初始化期间验证类文件,但这是为了防止栈上的参数不是所需格式或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检查,堆栈爆炸 对于本机方法,定义了签名处理程序。


  • 字节码模板生成



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

要执行该指令,VM规范要求将操作数放在Operand Stack中 ,但这不会阻止HotSpot将它们缓存在寄存器中。 为了确定堆栈顶部的当前状态,使用了一个枚举。


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

每个指令定义堆栈的TosState顶部的输入和输出状态,并且根据该状态发生模式。 这些模板在可读的模板表中初始化。 该表的一部分如下:


 // 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 , _ ); 

我们将对inoutgenerator列特别感兴趣。


in-指令开始时堆栈顶部的状态
out指令完成时堆栈顶部的状态
generator机器指令代码模板的生成器


所有字节码的模板的一般视图可以描述为:


  1. 如果未为指令设置调度位,则执行指令序言(x86上为no-op)


  2. 使用generator机器码


  3. 如果未为该指令设置调度位,则根据堆栈顶部的出栈状态(按顺序进入下一条指令)按顺序执行到下一条指令的转换。



结果模板的入口地址存储在全局表中,可用于调试。


在HotSpot中,以下相对笨拙的代码负责:


指令码生成器
 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/zh-CN469291/


All Articles