ITPub博客

首页 > IT职业 > IT职场 > HotSpot的执行引擎-CallStub栈帧

HotSpot的执行引擎-CallStub栈帧

IT职场 编辑:李佳惠 时间:2020-08-17 14:30:00 0 删除 编辑

之前多次提到接触到调用JavaCalls::call()方法来执行Java方法,如:

(1)Java主类装载时,调用JavaCalls::call()方法执行的Java方法checkAndLoadMain()方法

(2)类的初始化过程中,调用JavaCalls::call()方法执行的Java方法<clinit>方法

可以看出,JavaCalls::call()方法为虚拟机调用Java方法提供了便利,Java虚拟机有invokestatic、invokedynamic、invokestatic、invokespecial、invokevirtual几种方法调用指令,每个负责调用不同的方法,而这些方法都定义在JavaCalls类中,如下:

源代码位置:/src/share/vm/runtime/javaCalls.hpp
// All calls to Java have to go via JavaCalls. Sets up the stack frame
// and makes sure that the last_Java_frame pointers are chained correctly.
 
class  JavaCalls: AllStatic {
   static  void  call_helper(JavaValue* result, methodHandle* method, JavaCallArguments* args, TRAPS);
  public :
   // Optimized Constuctor call
   static  void  call_default_constructor(JavaThread* thread, methodHandle method, Handle receiver, TRAPS);
 
   // call_special
   // ------------
   // The receiver must be first oop in argument list
   // receiver表示方法的接收者,如A.main()调用中,A就是方法的接收者
   static  void  call_special(JavaValue* result, KlassHandle klass, Symbol* name,Symbol* signature, JavaCallArguments* args, TRAPS);
 
   static  void  call_special(JavaValue* result, Handle receiver, KlassHandle klass,Symbol* name, Symbol* signature, TRAPS); // No args
   static  void  call_special(JavaValue* result, Handle receiver, KlassHandle klass,Symbol* name, Symbol* signature, Handle arg1, TRAPS);
   static  void  call_special(JavaValue* result, Handle receiver, KlassHandle klass,Symbol* name, Symbol* signature, Handle arg1, Handle arg2, TRAPS);
 
   // virtual call
   // ------------
 
   // The receiver must be first oop in argument list
   static  void  call_virtual(JavaValue* result, KlassHandle spec_klass, Symbol* name,Symbol* signature, JavaCallArguments* args, TRAPS);
 
   static  void  call_virtual(JavaValue* result, Handle receiver, KlassHandle spec_klass,Symbol* name, Symbol* signature, TRAPS); // No args
   static  void  call_virtual(JavaValue* result, Handle receiver, KlassHandle spec_klass,Symbol* name, Symbol* signature, Handle arg1, TRAPS);
   static  void  call_virtual(JavaValue* result, Handle receiver, KlassHandle spec_klass,Symbol* name, Symbol* signature, Handle arg1, Handle arg2, TRAPS);
 
   // Static call
   // -----------
   static  void  call_static(JavaValue* result, KlassHandle klass,Symbol* name, Symbol* signature, JavaCallArguments* args, TRAPS);
 
   static  void  call_static(JavaValue* result, KlassHandle klass,Symbol* name, Symbol* signature, TRAPS);
   static  void  call_static(JavaValue* result, KlassHandle klass,Symbol* name, Symbol* signature, Handle arg1, TRAPS);
   static  void  call_static(JavaValue* result, KlassHandle klass,Symbol* name, Symbol* signature, Handle arg1, Handle arg2, TRAPS);
 
   // Low-level interface
   static  void  call(JavaValue* result, methodHandle method, JavaCallArguments* args, TRAPS);
};

上面的方法是自解释的,对应各自的invoke*指令,这些call_static()、call_virtual()函数内部调用了call()函数:

void  JavaCalls::call(JavaValue* result, methodHandle method, JavaCallArguments* args, TRAPS) {
   // Check if we need to wrap a potential OS exception handler around thread
   // This is used for e.g. Win32 structured exception handlers
   assert(THREAD->is_Java_thread(), "only JavaThreads can make JavaCalls" );
   // Need to wrap each and everytime, since there might be native code down the
   // stack that has installed its own exception handlers
   // 通过传入call_helper函数指针,在call_helper上面封装了异常的处理,典型的回调函数用法
   os::os_exception_wrapper(call_helper, result, &method, args, THREAD);
}

call()方法只是简单检查了一下线程信息,以及根据平台比如windows会使用结构化异常(SEH)包裹call_helper,最终执行方法调用的还是call_helper() 方法。调用链如下:

JavaCalls::call_helper()                      javaCalls.cpp
os::os_exception_wrapper()                    os_linux.cpp
JavaCalls::call()                             javaCalls.cpp
InstanceKlass::call_class_initializer_impl()  instanceKlass.cpp
InstanceKlass::call_class_initializer()       instanceKlass.cpp
InstanceKlass::initialize_impl()              instanceKlass.cpp
InstanceKlass::initialize()                   instanceKlass.cpp
InstanceKlass::initialize_impl()              instanceKlass.cpp
InstanceKlass::initialize()                   instanceKlass.cpp
initialize_class()                            thread.cpp   
Threads::create_vm()                          thread.cpp
JNI_CreateJavaVM()                            jni.cpp
InitializeJVM()                               java.c
JavaMain()                                    java.c

 JavaCalls::helper()函数的实现如下:

void  JavaCalls::call_helper(JavaValue* result, methodHandle* m, JavaCallArguments* args, TRAPS) {
   methodHandle method = *m;
   JavaThread* thread = (JavaThread*)THREAD;
   assert(thread->is_Java_thread(), "must be called by a java thread" );
   assert(method.not_null(), "must have a method to call" );
   assert(!SafepointSynchronize::is_at_safepoint(), "call to Java code during VM operation" );
   assert(!thread->handle_area()->no_handle_mark_active(), "cannot call out to Java here" );
 
 
   // Ignore call if method is empty
   if  (method->is_empty_method()) {
     assert(result->get_type() == T_VOID, "an empty method must return a void value" );
     return ;
   }
 
   assert(!thread->is_Compiler_thread(), "cannot compile from the compiler" );
   if  (CompilationPolicy::must_be_compiled(method)) {
     CompileBroker::compile_method(method, InvocationEntryBci,
                                   CompilationPolicy::policy()->initial_compile_level(),
                                   methodHandle(), 0, "must_be_compiled" , CHECK);
   }
 
   //获取的entry_point就是为Java方法调用准备栈桢,并把代码调用指针指向method的第一个字节码的内存地址。
   //entry_point相当于是method的封装,不同的method类型有不同的entry_point。
   // Since the call stub sets up like the interpreter we call the from_interpreted_entry
   // so we can go compiled via a i2c. Otherwise initial entry method will always
   // run interpreted.
   address entry_point = method->from_interpreted_entry();
   if  (JvmtiExport::can_post_interpreter_events() && thread->is_interp_only_mode()) {
     entry_point = method->interpreter_entry();
   }
 
   // Figure out if the result value is an oop or not (Note: This is a different value
   // than result_type. result_type will be T_INT of oops. (it is about size)
   BasicType result_type = runtime_type_from(result);
   bool  oop_result_flag = (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY);
 
   // NOTE: if we move the computation of the result_val_address inside
   // the call to call_stub, the optimizer produces wrong code.
   intptr_t* result_val_address = (intptr_t*)(result->get_value_addr());
 
   // Find receiver
   Handle receiver = (!method->is_static()) ? args->receiver() : Handle();
 
   // When we reenter Java, we need to reenable the yellow zone which
   // might already be disabled when we are in VM.
   if  (thread->stack_yellow_zone_disabled()) {
     thread->reguard_stack();
   }
 
   // Check that there are shadow pages available before changing thread state
   // to Java
   if  (!os::stack_shadow_pages_available(THREAD, method)) {
     // Throw stack overflow exception with preinitialized exception.
     Exceptions::throw_stack_overflow_exception(THREAD, __FILE__, __LINE__, method);
     return ;
   } else  {
     // Touch pages checked if the OS needs them to be touched to be mapped.
     os::bang_stack_shadow_pages();
   }
 
   // do call
   {
     JavaCallWrapper link(method, receiver, result, CHECK);
     {
       HandleMark hm(thread);  // HandleMark used by HandleMarkCleaner
       StubRoutines::call_stub()(
          (address)&link,
          // (intptr_t*)&(result->_value), // see NOTE above (compiler problem)
          result_val_address,              // see NOTE above (compiler problem)
          result_type,
          method(),
          entry_point,
          args->parameters(),
          args->size_of_parameters(),
          CHECK
       );
 
       result = link.result();  // circumvent MS C++ 5.0 compiler bug (result is clobbered across call)
       // Preserve oop return value across possible gc points
       if  (oop_result_flag) {
         thread->set_vm_result((oop) result->get_jobject());
       }
     }
   } // Exit JavaCallWrapper (can block - potential return oop must be preserved)
 
   // Check if a thread stop or suspend should be executed
   // The following assert was not realistic.  Thread.stop can set that bit at any moment.
   //assert(!thread->has_special_runtime_exit_condition(), "no async. exceptions should be installed");
 
   // Restore possible oop return
   if  (oop_result_flag) {
     result->set_jobject((jobject)thread->vm_result());
     thread->set_vm_result(NULL);
   }
}

我们需要关注此函数做的如下几件事:

1、检查目标方法是否“首次执行前就必须被编译”,是的话调用JIT编译器去编译目标方法

2、获取目标方法的解释模式入口from_interpreted_entry,也就是entry_point的值。获取的entry_point就是为Java方法调用准备栈桢,并把代码调用指针指向method的第一个字节码的内存地址。entry_point相当于是method的封装,不同的method类型有不同的entry_point

3、调用call_stub()函数。call_helper又可以分为两步,第一步判断一下方法是否为空,是否可以JIT编译,是否还有栈空间等,第二步StubRoutines::call_stub()实际调用os+cpu限定的方法。

调用CallStub函数的是/src/share/vm/runtime/javaCalls.cpp文件中的call_helper()函数,调用CallStub函数指针所指的函数时,需要传递8个参数,如下:

(1)link 此变量的类型为JavaCallWrapper,这个变量需要保存的信息很重要,后面将详细介绍。

(2)result_val_address 函数返回值地址。

(3)result_type 函数返回类型。 

(4)method() 当前要执行的方法。通过此参数可以获取到Java方法所有的元数据信息,包括最重要的字节码信息,这样就可以根据字节码信息解释执行这个方法了。

(5)entry_point HotSpot每次在调用Java函数时,必然会调用CallStub函数指针,这个函数指针的值为_call_stub_entry,HotSpot通过_call_stub_entry指向被调用函数地址,最终调用函数。在调用函数之前,必须要先经过entry_point,HotSpot实际是通过entry_point从method()对象上拿到Java方法对应的第1个字节码命令,这也是整个函数的调用入口。

(6)args->parameters()  描述Java函数的入参信息。

(7)args->size_of_parameters()  描述Java函数的入参数量。

(8)CHECK 当前线程对象。  

来源:/src/share/vm/runtime/stubRoutines.hpp
 
static  CallStub  call_stub() {
     return  CAST_TO_FN_PTR(CallStub, _call_stub_entry);
}

call_stub()函数返回一个函数指针,指向依赖于 操作系统和cpu架构的特定的方法,原因很简单,要执行native代码,得看看是什么cpu架构以便确定寄存器,看看什么os以便确定ABI。

其中CAST_TO_FN_PTR是宏,具体定义如下:

源代码位置:/src/share/vm/runtime/utilities/globalDefinitions.hpp
#define CAST_TO_FN_PTR(func_type, value) ((func_type)(castable_address(value)))

对call_stub()函数进行宏替换和展开后会变为如下的形式:

static  CallStub call_stub(){
     return  (CallStub)( castable_address(_call_stub_entry) );
}

CallStub的定义如下:

源代码位置:/src/share/vm/runtime/stubRoutines.hpp
 
// Calls to Java
typedef void  (*CallStub)(
     address   link, // 连接器
     intptr_t* result, // 函数返回值地址
     BasicType result_type, //函数返回类型
     Method* method, // JVM内部所表示的Java方法对象
     // JVM调用Java方法的例程入口。JVM内部的每一段例程都是在JVM启动过程中预先生成好的一段机器指令。要调用Java方法,
     // 必须经过本例程,即需要先执行这段机器指令,然后才能跳转到Java方法字节码所对应的机器指令去执行
     address   entry_point,
     intptr_t* parameters,
     int        size_of_parameters,
     TRAPS
); 

如上定义了一种函数指针类型,指向的函数声明了8个形式参数。 

在call_stub()函数中调用的castable_address()函数定义在globalDefinitions.hpp文件中,具体实现如下:

inline address_word  castable_address(address x)  {
     return  address_word(x) ;
}

address_word是一定自定义的类型,在globalDefinitions.hpp文件中的定义如下:

// unsigned integer which will hold a pointer
// except for some implementations of a C++
// linkage pointer to function. Should never
// need one of those to be placed in this type anyway.
typedef   uintptr_t     address_word;

其中uintptr_t也是一种自定义的类型,在Linux内核的操作系统下使用globalDefinitions_gcc.hpp文件中的定义,具体定义如下:

typedef  unsigned int   uintptr_t;

这样call_stub()函数其实等同于如下的实现形式:

static  CallStub call_stub(){
     return  (CallStub)( unsigned int (_call_stub_entry) );
}

将_call_stub_entry强制转换为unsigned int类型,然后以强制转换为CallStub类型。CallStub是一个函数指针,所以_call_stub_entry应该也是一个函数指针,而不应该是一个普通的无符号整数。  

在call_stub()函数中,_call_stub_entry的定义如下:

address StubRoutines::_call_stub_entry = NULL;

_call_stub_entry的初始化在在/src/cpu/x86/vm/stubGenerator_x86_64.cpp文件下的generate_initial()函数,调用链如下:

StubGenerator::generate_initial()   stubGenerator_x86_64.cpp   
StubGenerator::StubGenerator()      stubGenerator_x86_64.cpp
StubGenerator_generate()            stubGenerator_x86_64.cpp   
StubRoutines::initialize1()         stubRoutines.cpp   
stubRoutines_init1()                stubRoutines.cpp   
init_globals()                      init.cpp
Threads::create_vm()                thread.cpp
JNI_CreateJavaVM()                  jni.cpp
InitializeJVM()                     java.c
JavaMain()                          java.c

其中的StubGenerator类定义在src/cpu/x86/vm目录下的stubGenerator_x86_64.cpp文件中,这个文件中的generate_initial()方法会初始化call_stub_entry变量,如下:

StubRoutines::_call_stub_entry = generate_call_stub(StubRoutines::_call_stub_return_address);

调用的generate_call_stub()方法的实现如下:

address generate_call_stub(address& return_address) {
     assert(( int )frame::entry_frame_after_call_words == -( int )rsp_after_call_off + 1 &&
            ( int )frame::entry_frame_call_wrapper_offset == ( int )call_wrapper_off,
            "adjust this code" );
     StubCodeMark mark( this , "StubRoutines" , "call_stub" );
     address start = __ pc();
 
     // same as in generate_catch_exception()!
     const  Address rsp_after_call(rbp, rsp_after_call_off * wordSize);
 
     const  Address call_wrapper  (rbp, call_wrapper_off   * wordSize);
     const  Address result        (rbp, result_off         * wordSize);
     const  Address result_type   (rbp, result_type_off    * wordSize);
     const  Address method        (rbp, method_off         * wordSize);
     const  Address entry_point   (rbp, entry_point_off    * wordSize);
     const  Address parameters    (rbp, parameters_off     * wordSize);
     const  Address parameter_size(rbp, parameter_size_off * wordSize);
 
     // same as in generate_catch_exception()!
     const  Address thread        (rbp, thread_off         * wordSize);
 
     const  Address r15_save(rbp, r15_off * wordSize);
     const  Address r14_save(rbp, r14_off * wordSize);
     const  Address r13_save(rbp, r13_off * wordSize);
     const  Address r12_save(rbp, r12_off * wordSize);
     const  Address rbx_save(rbp, rbx_off * wordSize);
 
     // stub code
     __ enter();
     __ subptr(rsp, -rsp_after_call_off * wordSize);
 
     // save register parameters
     __ movptr(parameters,   c_rarg5); // parameters
     __ movptr(entry_point,  c_rarg4); // entry_point
 
 
     __ movptr(method,       c_rarg3); // method
     __ movl(result_type,  c_rarg2);   // result type
     __ movptr(result,       c_rarg1); // result
     __ movptr(call_wrapper, c_rarg0); // call wrapper
 
     // save regs belonging to calling function
     __ movptr(rbx_save, rbx);
     __ movptr(r12_save, r12);
     __ movptr(r13_save, r13);
     __ movptr(r14_save, r14);
     __ movptr(r15_save, r15);
 
     const  Address mxcsr_save(rbp, mxcsr_off * wordSize);
     {
       Label skip_ldmx;
       __ stmxcsr(mxcsr_save);
       __ movl(rax, mxcsr_save);
       __ andl(rax, MXCSR_MASK);    // Only check control and mask bits
       ExternalAddress mxcsr_std(StubRoutines::addr_mxcsr_std());
       __ cmp32(rax, mxcsr_std);
       __ jcc(Assembler::equal, skip_ldmx);
       __ ldmxcsr(mxcsr_std);
       __ bind(skip_ldmx);
     }
 
 
     // Load up thread register
     __ movptr(r15_thread, thread);
     __ reinit_heapbase();
 
 
     // pass parameters if any
     BLOCK_COMMENT( "pass parameters if any" );
     Label parameters_done;
     __ movl(c_rarg3, parameter_size);
     __ testl(c_rarg3, c_rarg3);
     __ jcc(Assembler::zero, parameters_done);
 
     Label loop;
     __ movptr(c_rarg2, parameters);       // parameter pointer
     __ movl(c_rarg1, c_rarg3);            // parameter counter is in c_rarg1
     __ BIND(loop);
     __ movptr(rax, Address(c_rarg2, 0)); // get parameter
     __ addptr(c_rarg2, wordSize);       // advance to next parameter
     __ decrementl(c_rarg1);             // decrement counter
     __ push(rax);                       // pass parameter
     __ jcc(Assembler::notZero, loop);
 
     // call Java function
     __ BIND(parameters_done);
     __ movptr(rbx, method);             // get Method*
     __ movptr(c_rarg1, entry_point);    // get entry_point
     __ mov(r13, rsp);                   // set sender sp
     BLOCK_COMMENT( "call Java function" );
     __ call(c_rarg1);
 
     BLOCK_COMMENT( "call_stub_return_address:" );
     return_address = __ pc();
 
     // store result depending on type (everything that is not
     // T_OBJECT, T_LONG, T_FLOAT or T_DOUBLE is treated as T_INT)
     __ movptr(c_rarg0, result);
     Label is_long, is_float, is_double, exit;
     __ movl(c_rarg1, result_type);
     __ cmpl(c_rarg1, T_OBJECT);
     __ jcc(Assembler::equal, is_long);
     __ cmpl(c_rarg1, T_LONG);
     __ jcc(Assembler::equal, is_long);
     __ cmpl(c_rarg1, T_FLOAT);
     __ jcc(Assembler::equal, is_float);
     __ cmpl(c_rarg1, T_DOUBLE);
     __ jcc(Assembler::equal, is_double);
 
     // handle T_INT case
     __ movl(Address(c_rarg0, 0), rax);
 
     __ BIND(exit);
 
     // pop parameters
     __ lea(rsp, rsp_after_call);
 
 
     __ movptr(r15, r15_save);
     __ movptr(r14, r14_save);
     __ movptr(r13, r13_save);
     __ movptr(r12, r12_save);
     __ movptr(rbx, rbx_save);
 
     __ ldmxcsr(mxcsr_save);
 
     // restore rsp
     __ addptr(rsp, -rsp_after_call_off * wordSize);
 
     // return
     __ pop(rbp);
     __ ret(0);
 
     // handle return types different from T_INT
     __ BIND(is_long);
     __ movq(Address(c_rarg0, 0), rax);
     __ jmp(exit);
 
     __ BIND(is_float);
     __ movflt(Address(c_rarg0, 0), xmm0);
     __ jmp(exit);
 
     __ BIND(is_double);
     __ movdbl(Address(c_rarg0, 0), xmm0);
     __ jmp(exit);
 
     return  start;
   }

这个函数实现的逻辑有点多,而且最终会生成一段机器码,由于机器码很难读懂,所以我们可以通过方法的源代码和汇编代码来解读。

首先简单介绍一下address和Address类型。

address是u_char*类型的别名,定义如下:

源代码位置:globalDefinitions.hpp
 
typedef   u_char*       address;

Address类的定义如下:

源代码位置:/x86/vm/assembler_x86.hpp
 
// Address is an abstraction used to represent a memory location
// using any of the amd64 addressing modes with one object.
//
// Note: A register location is represented via a Register, not
//       via an address for efficiency & simplicity reasons.
 
class  Address VALUE_OBJ_CLASS_SPEC {
    ...
}

如果要看generate_call_stub()方法生成的汇编,可以在导入hsdis-amd64.so的情况下,输入如下命令:

-XX:+PrintStubCode -XX:+UnlockDiagnosticVMOptions    com.test/CompilationDemo1

首先看generate_call_stub()方法如下两句代码:

// stub code
__ enter();
__ subptr(rsp, -rsp_after_call_off * wordSize);

调用macroAssembler_x86.cpp文件中的enter()方法, 用来保存调用方栈基址,并将call_stub栈基址更新为当前栈顶地址。实现如下:

void  MacroAssembler::enter() {
   push(rbp);
   mov(rbp, rsp);
}

调用的push()方法如下:

void  Assembler::push(Register src) {
   int  encode = prefix_and_encode(src->encoding());
 
   emit_int8(0x50 | encode);
} 

Assembler中定义的一些方法通常难以读懂,这是因为需要我们知道x86体系下机器码,并且要对Opcode编码规则有所掌握,这一部分后面会详细介绍,这里暂时不介绍,有兴趣的可以自行学习Intel开发者手册,里面对Intel cpu指令集有详细介绍。我们这里只简单认识一下生成机器码的相关方法即可。

调用的src->encoding()返回自身,而prefix_and_encode()方法的实现如下:

int  Assembler::prefix_and_encode( int  reg_enc, bool  byteinst) {
   if  (reg_enc >= 8) {
     prefix(REX_B);
     reg_enc -= 8;
   } else  if  (byteinst && reg_enc >= 4) {
     prefix(REX);
   }
   return  reg_enc;
}

enter()方法中调用的mov()方法的实现如下:

void  Assembler::mov(Register dst, Register src) {
   LP64_ONLY(movq(dst, src)) NOT_LP64(movl(dst, src));
}

对于64位来说,调用movq()方法,如下:

void  Assembler::movq(Register dst, Register src) {
   int  encode = prefixq_and_encode(dst->encoding(), src->encoding());
   emit_int8((unsigned char )0x8B);
   emit_int8((unsigned char )(0xC0 | encode));
}

调用prefixq_and_encode()方法的实现如下:

int  Assembler::prefixq_and_encode( int  dst_enc, int  src_enc) {
   if  (dst_enc < 8) {
     if  (src_enc < 8) {
       prefix(REX_W);
     } else  {
       prefix(REX_WB);
       src_enc -= 8;
     }
   } else  {
     if  (src_enc < 8) {
       prefix(REX_WR);
     } else  {
       prefix(REX_WRB);
       src_enc -= 8;
     }
     dst_enc -= 8;
   }
   return  dst_enc << 3 | src_enc;
}

dst_enc的值为5,src_enc的值为4。 

generate_call_stub()方法中调用的subptr()方法的实现如下:

void  MacroAssembler::subptr(Register dst, int32_t imm32) {
   LP64_ONLY(subq(dst, imm32)) NOT_LP64(subl(dst, imm32));
}

调用的 subq()方法的实现如下:

void  Assembler::subq(Register dst, int32_t imm32) {
   ( void ) prefixq_and_encode(dst->encoding());
   emit_arith(0x81, 0xE8, dst, imm32);
}

调用的prefixq_and_encode()方法的实现如下:

int  Assembler::prefixq_and_encode( int  reg_enc) {
   if  (reg_enc < 8) {
     prefix(REX_W);
   } else  {
     prefix(REX_WB);
     reg_enc -= 8;
   }
   return  reg_enc;
}

subq()方法中调用的emit_arith()方法的实现如下: