zoukankan      html  css  js  c++  java
  • CallStub栈帧

    之前多次提到接触到调用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()方法的实现如下:

    void Assembler::emit_arith(int op1, int op2, Register dst, int32_t imm32) {
      assert(isByte(op1) && isByte(op2), "wrong opcode");
      assert((op1 & 0x01) == 1, "should be 32bit operation");
      assert((op1 & 0x02) == 0, "sign-extension bit should not be set");
    
      if (is8bit(imm32)) {
        emit_int8(op1 | 0x02); // set sign bit
        emit_int8(op2 | encode(dst));
        emit_int8(imm32 & 0xFF);
      } else {
        emit_int8(op1);
        emit_int8(op2 | encode(dst));
        emit_int32(imm32);
      }
    }

    使用参数命令:

    -XX:+UnlockDiagnosticVMOptions -XX:+PrintStubCode

    可以输出generate_call_stub方法生成的汇编,生成的汇编代码如下:

    StubRoutines::call_stub [0x00007fdf4500071f, 0x00007fdf45000807[ (232 bytes)
      0x00007fdf4500071f: push   %rbp         
      0x00007fdf45000720: mov    %rsp,%rbp 
      0x00007fdf45000723: sub    $0x60,%rsp  // 0x60 = -rsp_after_call_of * wordSize

    如上汇编第1个为源操作数,第2个为目地操作数。如上3句汇编通常是开辟一个新栈固定的格式。

    继续看generate_call_stub()方法的实现,如下:

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

    生成的汇编代码如下:

    0x00007fdf45000727: mov      %r9,-0x8(%rbp)
    0x00007fdf4500072b: mov      %r8,-0x10(%rbp)
    0x00007fdf4500072f: mov      %rcx,-0x18(%rbp)
    0x00007fdf45000733: mov      %edx,-0x20(%rbp)
    0x00007fdf45000736: mov      %rsi,-0x28(%rbp)
    0x00007fdf4500073a: mov      %rdi,-0x30(%rbp)
    0x00007fdf4500073e: mov      %rbx,-0x38(%rbp)
    0x00007fdf45000742: mov      %r12,-0x40(%rbp)
    0x00007fdf45000746: mov      %r13,-0x48(%rbp)
    0x00007fdf4500074a: mov      %r14,-0x50(%rbp)
    0x00007fdf4500074e: mov      %r15,-0x58(%rbp)
    // stmxcsr是将MXCSR寄存器中的值保存到-0x60(%rbp)中
    0x00007fdf45000752: stmxcsr  -0x60(%rbp)  
    0x00007fdf45000756: mov      -0x60(%rbp),%eax
    0x00007fdf45000759: and      $0xffc0,%eax
    // cmp通过第2个操作数减去第1个操作数的差,根据结果来设置eflags中的标志位。
    // 本质上和sub指令相同,但是不会改变操作数的值
    0x00007fdf4500075f: cmp      0x1762cb5f(%rip),%eax  # 0x00007fdf5c62d2c4 
    // 当ZF=1时跳转到目标地址
    0x00007fdf45000765: je       0x00007fdf45000772 
    // 将m32加载到MXCSR寄存器中
    0x00007fdf4500076b: ldmxcsr  0x1762cb52(%rip)      # 0x00007fdf5c62d2c4   
    

    MXCSR状态管理指令,ldmxcsr与stmxcsr,用于控制MXCSR寄存器(表示SSE指令的运算状态的寄存器)状态。ldmxcsr指令从存储器中加载MXCSR寄存器状态;stmxcsr指令将MXCSR寄存器状态保存到存储器中。

    最终的栈帧状态如下图所示。

     

    由于call_helper()函数在调用CallStub()函数时,传递的参数多于6个,所以最后2个参数size_of_parameters与TRAPS(当前线程)要通过call_helper()的栈帧来传递,剩下的可以根据调用约定通过寄存器来传递。

    可以看到在传递参数时会遵守调用约定,当x64中函数调用时,以下寄存器用于参数: 

    • 第1个参数:rdi    c_rarg0
    • 第2个参数:rsi    c_rarg1
    • 第3个参数:rdx   c_rarg2
    • 第4个参数:rcx   c_rarg3
    • 第5个参数:r8     c_rarg4
    • 第6个参数:r9     c_rarg5

    在函数调用时,6个及小于6个用如下寄存器来传递,在HotSpot中通过更易理解的别名c_rarg*来使用对应的寄存器。如果参数超过六个,那么程序调用栈就会被用来传递那些额外的参数。 

    继续看generate_call_stub()方法的实现,接来下会加载线程寄存器,代码如下:

    // Load up thread register
    __ movptr(r15_thread, thread);
    __ reinit_heapbase();

    生成的汇编代码如下:

    0x00007fdf45000772: mov    0x18(%rbp),%r15
    0x00007fdf45000776: mov    0x1764212b(%rip),%r12   # 0x00007fdf5c6428a8

    如果在调用函数时有参数的话需要传递参数,代码如下:

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

    这里是个循环,用于传递参数,相当于如下代码:

    while(%esi){
       rax = *arg
       push_arg(rax)
       arg++;   // ptr++
       %esi--;  // counter--
    }
    

    生成的汇编代码如下:

    0x00007fdf4500077d: mov    0x10(%rbp),%ecx    // 将栈中parameter size送到%ecx中
    0x00007fdf45000780: test   %ecx,%ecx          // 做与运算,只有当%ecx中的值为0时才等于0
    0x00007fdf45000782: je     0x00007fdf4500079a // 没有参数需要传递,直接跳转到parameters_done即可
    // -- loop --
    // 汇编执行到这里,说明paramter size不为0,需要传递参数
    0x00007fdf45000788: mov    -0x8(%rbp),%rdx
    0x00007fdf4500078c: mov    %ecx,%esi
    0x00007fdf4500078e: mov    (%rdx),%rax
    0x00007fdf45000791: add    $0x8,%rdx
    0x00007fdf45000795: dec    %esi
    0x00007fdf45000797: push   %rax
    0x00007fdf45000798: jne    0x00007fdf4500078e  // 跳转到loop
    

    因为要调用Java方法,所以会为Java方法压入实际的参数,也就是压入parameter size个从parameters开始取的参数。压入参数后的栈如下图所示。

    调用Java函数,如下:

    // call Java function
    // -- parameters_done -- __ BIND(parameters_done); __ movptr(rbx, method); // get Method* __ movptr(c_rarg1, entry_point); // get entry_point __ mov(r13, rsp); // set sender sp __ call(c_rarg1); // 调用Java方法

    生成的汇编代码如下:

    0x00007fdf4500079a: mov     -0x18(%rbp),%rbx  // 将Method*送到%rbx中
    0x00007fdf4500079e: mov     -0x10(%rbp),%rsi  // 将entry_point送到%rsi中
    0x00007fdf450007a2: mov     %rsp,%r13         // 将调用者的栈顶指针保存到%r13中
    0x00007fdf450007a5: callq   *%rsi             // 调用Java方法

    注意调用callq指令后,会将callq指令的下一条指令的地址压栈,再跳转到第1操作数指定的地址,也就是*%rsi表示的地址。压入下一条指令的地址是为了让函数能通过跳转到栈上的地址从子函数返回。 

    callq指令调用的是entry point。entry point在后面会详细介绍。

    接下来在generate_call_stub()方法中会处理调用Java方法后的返回值与返回类型,而且还需要执行退栈操作,也就是将栈恢复到调用Java方法之前的状态。代码实现如下:

    // store result depending on type (everything that is not
    // T_OBJECT, T_LONG, T_FLOAT or T_DOUBLE is treated as T_INT)
    // 保存方法调用结果依赖于结果类型,只要不是T_OBJECT, T_LONG, T_FLOAT or T_DOUBLE,都当做T_INT处理
    // 将result地址的值拷贝到c_rarg0中,也就是将方法调用的结果保存在rdi寄存器中,注意result为函数返回值的地址
    __ movptr(c_rarg0, result);     
    Label is_long, is_float, is_double, exit;
    // 将result_type地址的值拷贝到c_rarg1中,也就是将方法调用的结果返回的类型保存在esi寄存器中
    
    
    __ 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
    // 当执行到这里时,处理的就是T_INT类型,将rax中的值写入c_rarg0保存的地址指向的内存中
    __ movl(Address(c_rarg0, 0), rax); // 调用函数后返回值根据调用约定会存储在eax中
    
    __ BIND(exit);
    
    // pop parameters
    // 将rsp_after_call中保存的有效地址拷贝到rsp中,即将rsp往高地址方向移动了,
    // 原来的方法调用参数argument1、...、argumentn相当于从栈中弹出
    __ lea(rsp, rsp_after_call);  // lea指令将地址加载到寄存器中
    

    生成的汇编代码如下:

    0x00007fdf450007a7: mov    -0x28(%rbp),%rdi  //  栈中的-0x28位置保存result
    0x00007fdf450007ab: mov    -0x20(%rbp),%esi  //  栈中的-0x20位置保存result type
    0x00007fdf450007ae: cmp    $0xc,%esi         // 是否为T_OBJECT类型
    0x00007fdf450007b1: je     0x00007fdf450007f6
    0x00007fdf450007b7: cmp    $0xb,%esi         // 是否为T_LONG类型
    0x00007fdf450007ba: je     0x00007fdf450007f6
    0x00007fdf450007c0: cmp    $0x6,%esi         // 是否为T_FLOAT类型
    0x00007fdf450007c3: je     0x00007fdf450007fb
    0x00007fdf450007c9: cmp    $0x7,%esi         // 是否为T_DOUBLE类型
    0x00007fdf450007cc: je     0x00007fdf45000801
    
    0x00007fdf450007d2: mov    %eax,(%rdi)       // 如果是T_INT类型,直接将返回结果%eax写到栈中-0x28的位置
    
    // -- exit --
    0x00007fdf450007d4: lea    -0x60(%rbp),%rsp  // 将rsp_after_call的有效地址拷到rsp中
    

    恢复之前保存的caller-save寄存器:

    // restore regs belonging to calling function
    __ movptr(r15, r15_save);
    __ movptr(r14, r14_save);
    __ movptr(r13, r13_save);
    __ movptr(r12, r12_save);
    __ movptr(rbx, rbx_save);
    
    __ ldmxcsr(mxcsr_save); 

    生成的汇编代码如下:

    0x00007fdf450007d8: mov      -0x58(%rbp),%r15
    0x00007fdf450007dc: mov      -0x50(%rbp),%r14
    0x00007fdf450007e0: mov      -0x48(%rbp),%r13
    0x00007fdf450007e4: mov      -0x40(%rbp),%r12
    0x00007fdf450007e8: mov      -0x38(%rbp),%rbx
    0x00007fdf450007ec: ldmxcsr  -0x60(%rbp)
    

    在弹出了为调用Java方法保存的调用参数及恢复caller-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); 

    生成的汇编代码如下:

    // %rsp加上0x60,也就是执行退栈操作,也就相当于弹出了callee_save寄存器和压栈的那6个参数
    0x00007fdf450007f0: add    $0x60,%rsp 
    0x00007fdf450007f4: pop    %rbp
    0x00007fdf450007f5: retq  // 方法返回,指令中的q表示64位操作数,就是指的栈中存储的return address是64位的
    
    // -- is_long --
    0x00007fdf450007f6: mov    %rax,(%rdi)
    0x00007fdf450007f9: jmp    0x00007fdf450007d4
    
    // -- is_float --
    0x00007fdf450007fb: vmovss %xmm0,(%rdi)
    0x00007fdf450007ff: jmp    0x00007fdf450007d4
    
    // -- is_double --
    0x00007fdf45000801: vmovsd %xmm0,(%rdi)
    0x00007fdf45000805: jmp    0x00007fdf450007d4
    

    在执行完add指令后的栈状态如下图所示。  

     

    然后恢复%rsp的值后,调用retq使用return address返回调用call_helper()函数的那个调用函数,至于paramter size与thread则由调用函数负责释放。 

    相关文章的链接如下:

    1、在Ubuntu 16.04上编译OpenJDK8的源代码 

    2、调试HotSpot源代码

    3、HotSpot项目结构 

    4、HotSpot的启动过程 

    5、HotSpot二分模型(1)

    6、HotSpot的类模型(2)  

    7、HotSpot的类模型(3) 

    8、HotSpot的类模型(4)

    9、HotSpot的对象模型(5)  

    10、HotSpot的对象模型(6) 

    11、操作句柄Handle(7)

    12、句柄Handle的释放(8)

    13、类加载器 

    14、类的双亲委派机制 

    15、核心类的预装载

    16、Java主类的装载  

    17、触发类的装载  

    18、类文件介绍 

    19、文件流 

    20、解析Class文件 

    21、常量池解析(1) 

    22、常量池解析(2)

    23、字段解析(1)

    24、字段解析之伪共享(2) 

    25、字段解析(3)  

    26、字段解析之OopMapBlock(4)

    27、方法解析之Method与ConstMethod介绍  

    28、方法解析

    29、klassVtable与klassItable类的介绍  

    30、计算vtable的大小 

    31、计算itable的大小 

    32、解析Class文件之创建InstanceKlass对象 

    33、字段解析之字段注入 

    34、类的连接  

    35、类的连接之验证 

    36、类的连接之重写(1) 

    37、类的连接之重写(2)

    38、方法的连接  

    39、初始化vtable 

    40、初始化itable  

    41、类的初始化 

    42、对象的创建  

    43、Java引用类型 

    44、Java引用类型之软引用(1)

    45、Java引用类型之软引用(2)

    46、Java引用类型之弱引用与幻像引用  

    47、Java引用类型之最终引用

    48、HotSpot的垃圾回收算法  

    49、HotSpot的垃圾回收器   

    作者持续维护的个人博客  classloading.com

    关注公众号,有HotSpot源码剖析系列文章!

       

    参考文章:

    (1)JVM系列之 _call_stub_entry初始化

    (2)[Inside HotSpot] Java的方法调用

      

  • 相关阅读:
    如何复制百度文库中的文章转的,不用担心下载要币了[转]
    什么是中间件(转)
    android实用代码 (转)
    [Java]读取文件方法大全(转)
    Solaris下查看磁盘、内存、CPU使用程度
    Gene Ontology (GO) 简介
    如何在网上查某个基因的转录因子及启动子
    kmeans k均值聚类的弱点/缺点
    层次聚类
    什么是非负矩阵分解 NMF(Nonnegative Matrix Factorization )
  • 原文地址:https://www.cnblogs.com/mazhimazhi/p/13516197.html
Copyright © 2011-2022 走看看