JVM调用CallStaticVoidMethod的实现java方法调用

3,651 阅读10分钟

紧接着上一篇JVM启动中通过JNIEnv的CallStaticVoidMethod实现对java中main函数的调用。

JNIEnv->CallStaticVoidMethod的方法实现

这里需要首先JNI_ENTRY这个宏是定义,它与JNI_END之间的是函数体。

  1. 传入CallStaticVoidMethod字符串,调用JNIWrapper的构造函数初始化。
  2. va_lis、va_start是可变参数的宏定义.
  3. 创建JNI_ArgumentPusherVaArg对象。
  4. jni_invoke_static调用静态方法,传入JNIEnv、JNI_STATIC(调用类型)、上一篇文章获取的java的main方法的id以及参数对象JNI_ArgumentPusherVaArg.
JNI_ENTRY(void, jni_CallStaticVoidMethod(JNIEnv *env, jclass cls, jmethodID methodID, ...))
  JNIWrapper("CallStaticVoidMethod");
  va_list args;
  va_start(args, methodID);
  JavaValue jvalue(T_VOID);
  JNI_ArgumentPusherVaArg ap(methodID, args);
  jni_invoke_static(env, &jvalue, NULL, JNI_STATIC, methodID, &ap, CHECK);
  va_end(args);
JNI_END

jni_invoke_static方法的实现

  1. 调用Method的resolve_jmethod_id方法返回Method指针,创建methodHandle对象,它是定义handles.hpp中宏定义的类.实际就Mehtod的代理。
  2. 创建JavaCallArguments对象,并设置调用参数。
  3. 调用 JavaCalls::call传入result, method, &java_args参数.
  4. 对调用返回结果判断是否 T_OBJECT,T_ARRAY,并调用set_jobject设置结果.
static void jni_invoke_static(JNIEnv *env, JavaValue* result, jobject receiver, JNICallType call_type, jmethodID method_id, JNI_ArgumentPusher *args, TRAPS) {
  methodHandle method(THREAD, Method::resolve_jmethod_id(method_id));

  // Create object to hold arguments for the JavaCall, and associate it with
  // the jni parser
  ResourceMark rm(THREAD);
  int number_of_parameters = method->size_of_parameters();
  JavaCallArguments java_args(number_of_parameters);
  args->set_java_argument_object(&java_args);

  assert(method->is_static(), "method should be static");

  // Fill out JavaCallArguments object
  args->iterate( Fingerprinter(method).fingerprint() );
  // Initialize result type
  result->set_type(args->get_ret_type());

  // Invoke the method. Result is returned as oop.
  JavaCalls::call(result, method, &java_args, CHECK);

  // Convert result
  if (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY) {
    result->set_jobject(JNIHandles::make_local(env, (oop) result->get_jobject()));
  }
}

JavaCalls的call方法实现

  1. 校验当前线程是否是java线程.
  2. os::os_exception_wrapper里面包装就是对call_helper函数的调用
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 every time, since there might be native code down the
  // stack that has installed its own exception handlers
  os::os_exception_wrapper(call_helper, result, &method, args, THREAD);
}

call_helper方法实现

  1. 获取当前java线程指针.
  2. 调用verify对参数进行验证。
  3. 调用mehtod的from_interpreted_entry方法获取解释器的指令入口地址.
  4. 调用runtime_type_from获取函数返回类型,判断是否是 T_OBJECT或T_ARRAY类型.
  5. 调用result_val_address获取返回值地址
  6. 判断方法是static,如果是则调用参数中receiver函数返回的Handle,不是则返回空Handle对象。
  7. 调用thread的stack_yellow_zone_disabled是否为真,然后调用其reguard_stack校验栈。
  8. 调用os的stack_shadow_pages_available判断os是否有shadow pages可用,如果没有则抛异常,否则调用bang_stack_shadow_pages。
  9. 调用JavaCallWrapper构造器,创建对象并赋值给link变量.
  10. 创建HandleMark对象。
  11. 调用StubRoutines::call_stub()并传入一下8个参数
    (address)&link, JavaCallWrapper对象 result_val_address, 返回值地址 result_type, \ 返回类型 method(),\ 当前要执行的方法 entry_point,\ 整个函数的调用入口 args->parameters(),\ 实际参数 args->size_of_parameters(),\ 参数大小 CHECK //当前线程对象
  12. 获取link对象(实际是JavaCallWrapper类型)调用的返回值。
  13. 如果返回值是oop类型,则调用thread的set_vm_result设置result的get_jobject的值。
  14. JavaCallWrapper执行退出后,再次判断返回值是oop类型,重新设置thread之前设置的Vm_result,然后重置thread的vm_reuslt为NULL。(这个重新设置主要是为了规避C++ 5.0的必包调用后,返回值也没有了,所以是先用thread的vm_result存储了调用返回值)
void JavaCalls::call_helper(JavaValue* result, methodHandle* m, JavaCallArguments* args, TRAPS) {
  methodHandle method = *m;
  JavaThread* thread = (JavaThread*)THREAD;
  // Verify the arguments
  if (CheckJNICalls)  {
    args->verify(method, result->get_type(), thread);
  }
  // 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();
 
  BasicType result_type = runtime_type_from(result);
  bool oop_result_flag = (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY);

  intptr_t* result_val_address = (intptr_t*)(result->get_value_addr());

  // Find receiver
  Handle receiver = (!method->is_static()) ? args->receiver() : Handle();

  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),
        result_val_address,        
        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);
  }
}

StubRoutines的call_stub的实现

call_stub方法返回CAST_TO_FN_PTR宏定义

#define CAST_TO_FN_PTR(func_type, value) ((func_type)(castable_address(value)))

static CallStub call_stub() {
   return CAST_TO_FN_PTR(CallStub, _call_stub_entry); 
}

将CAST_TO_FN_PTR(CallStub, _call_stub_entry)宏定义展开如下

  ((CallStub))(castable_address(_call_stub_entry))

其中castable_address是将_call_stub_entry转换为unsigned int类型的函数地址.

typedef unsigned int uintptr_t;
typedef uintptr_t     address_word;
inline address_word  castable_address(void* x)             
{ return address_word(x) ; }

下面就是StubRoutines::call_stub()的传入的8个参数正好是CallStub这个函数指针的定义的参数,也就是将_call_stub_entry转成CallStub指针对象。

  // Calls to Java
  typedef void (*CallStub)(
    address   link,
    intptr_t* result,
    BasicType result_type,
    Method* method,
    address   entry_point,
    intptr_t* parameters,
    int       size_of_parameters,
    TRAPS
  );

_call_stub_entry变量是在stubGenerator_x86_64.cpp文件进行初始化,这样就可以找到函数指针的调用入口。

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

StubGenerator类中generate_call_stub里面代码如下:

 StubCodeMark mark(this, "StubRoutines", "call_stub");
    address start = __ pc();

    // stub code parameters / addresses
    assert(frame::entry_frame_call_wrapper_offset == 2, "adjust this code");
    bool  sse_save = false;
    const Address rsp_after_call(rbp, -4 * wordSize); // same as in generate_catch_exception()!
    const int     locals_count_in_bytes  (4*wordSize);
    const Address mxcsr_save    (rbp, -4 * wordSize);
    const Address saved_rbx     (rbp, -3 * wordSize);
    const Address saved_rsi     (rbp, -2 * wordSize);
    const Address saved_rdi     (rbp, -1 * wordSize);
    const Address result        (rbp,  3 * wordSize);
    const Address result_type   (rbp,  4 * wordSize);
    const Address method        (rbp,  5 * wordSize);
    const Address entry_point   (rbp,  6 * wordSize);
    const Address parameters    (rbp,  7 * wordSize);
    const Address parameter_size(rbp,  8 * wordSize);
    const Address thread        (rbp,  9 * wordSize); // same as in generate_catch_exception()!
    sse_save =  UseSSE > 0;

    // stub code
    __ enter();
    __ movptr(rcx, parameter_size);              // parameter counter
    __ shlptr(rcx, Interpreter::logStackElementSize); // convert parameter count to bytes
    __ addptr(rcx, locals_count_in_bytes);       // reserve space for register saves
    __ subptr(rsp, rcx);
    __ andptr(rsp, -(StackAlignmentInBytes));    // Align stack

    // save rdi, rsi, & rbx, according to C calling conventions
    __ movptr(saved_rdi, rdi);
    __ movptr(saved_rsi, rsi);
    __ movptr(saved_rbx, rbx);
    // save and initialize %mxcsr
    if (sse_save) {
      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);
    }

    // make sure the control word is correct.
    __ fldcw(ExternalAddress(StubRoutines::addr_fpu_cntrl_wrd_std()));

    // pass parameters if any
    BLOCK_COMMENT("pass parameters if any");
    Label parameters_done;
    __ movl(rcx, parameter_size);  // parameter counter
    __ testl(rcx, rcx);
    __ jcc(Assembler::zero, parameters_done);

    // parameter passing loop
    Label loop;
    // Copy Java parameters in reverse order (receiver last)
    // Note that the argument order is inverted in the process
    // source is rdx[rcx: N-1..0]
    // dest   is rsp[rbx: 0..N-1]

    __ movptr(rdx, parameters);          // parameter pointer
    __ xorptr(rbx, rbx);

    __ BIND(loop);

    // get parameter
    __ movptr(rax, Address(rdx, rcx, Interpreter::stackElementScale(), -wordSize));
    __ movptr(Address(rsp, rbx, Interpreter::stackElementScale(),
                    Interpreter::expr_offset_in_bytes(0)), rax);          // store parameter
    __ increment(rbx);
    __ decrement(rcx);
    __ jcc(Assembler::notZero, loop);

    // call Java function
    __ BIND(parameters_done);
    __ movptr(rbx, method);           // get Method*
    __ movptr(rax, entry_point);      // get entry_point
    __ mov(rsi, rsp);                 // set sender sp
    BLOCK_COMMENT("call Java function");
    __ call(rax);

    BLOCK_COMMENT("call_stub_return_address:");
    return_address = __ pc();

    // store result depending on type
    // (everything that is not T_LONG, T_FLOAT or T_DOUBLE is treated as T_INT)
    __ movptr(rdi, result);
    Label is_long, is_float, is_double, exit;
    __ movl(rsi, result_type);
    __ cmpl(rsi, T_LONG);
    __ jcc(Assembler::equal, is_long);
    __ cmpl(rsi, T_FLOAT);
    __ jcc(Assembler::equal, is_float);
    __ cmpl(rsi, T_DOUBLE);
    __ jcc(Assembler::equal, is_double);

    // handle T_INT case
    __ movl(Address(rdi, 0), rax);
    __ BIND(exit);

    // check that FPU stack is empty
    __ verify_FPU(0, "generate_call_stub");

    // pop parameters
    __ lea(rsp, rsp_after_call);

    // restore %mxcsr
    if (sse_save) {
      __ ldmxcsr(mxcsr_save);
    }

    // restore rdi, rsi and rbx,
    __ movptr(rbx, saved_rbx);
    __ movptr(rsi, saved_rsi);
    __ movptr(rdi, saved_rdi);
    __ addptr(rsp, 4*wordSize);

    // return
    __ pop(rbp);
    __ ret(0);

    // handle return types different from T_INT
    __ BIND(is_long);
    __ movl(Address(rdi, 0 * wordSize), rax);
    __ movl(Address(rdi, 1 * wordSize), rdx);
    __ jmp(exit);

    __ BIND(is_float);
    // interpreter uses xmm0 for return values
    if (UseSSE >= 1) {
      __ movflt(Address(rdi, 0), xmm0);
    } else {
      __ fstp_s(Address(rdi, 0));
    }
    __ jmp(exit);

    __ BIND(is_double);
    // interpreter uses xmm0 for return values
    if (UseSSE >= 2) {
      __ movdbl(Address(rdi, 0), xmm0);
    } else {
      __ fstp_d(Address(rdi, 0));
    }
    __ jmp(exit);

    return start;
  }

首先来解释一下address start = __ pc()这个调用enter函数前面的两个_,代表的意思什么?从下面的源码看到__原来是宏定义,它是在stubGenerator_x86_32.cpp中。

#define __ _masm->

masm是StubGenerator的父类中 StubCodeGenerator的私有变量,那么_ pc()就等同于 _masm-> pc(),实际就是调用MacroAssembler对象的enter方法.

class StubCodeGenerator: public StackObj {
 protected:
  MacroAssembler*  _masm;

那么接下来就是翻译汇编指令(这里是以X86 32位平台为例分析):

  1. 首先是通过__ pc()是获取的代码的的end地址作为pc地址,作为赋值给地址变量start.
CodeSection*  code_section() const   { return _code_section; }
address       pc()           const   { return code_section()->end(); }
  1. 校验frame::entry_frame_call_wrapper_offset 等于2,X86平台初始化是2。
  2. 定义站中定义rsp_after_call、mxcsr_save、saved_rbx、saved_rsi、saved_rdi、result、result_type、method、entry_point、parameters、parameter_size、thread存储地址的位置.
  3. __ enter()就是开辟栈帧,首先push(rbp)是在栈上保存rbp栈基址, 这里0x50执行对应汇编指令是0x50  push eax, 由于rbp寄存器encoding值是3,所以谢日汇编操作码是0x53,对应汇编就是push ebp,保存上一个方法方法栈基址,mov(rbp, rsp)也是类似的翻译成mov rbp,rsp,将最新的rsp赋值到rbp,作为被调用方法栈栈帧的基地址.
CONSTANT_REGISTER_DECLARATION(Register, rbx,    (3));

void MacroAssembler::enter() {
 push(rbp);
 mov(rbp, rsp);
}
void Assembler::push(Register src) {
 int encode = prefix_and_encode(src->encoding());
 emit_int8(0x50 | encode);
}
 void emit_int8(   int8_t  x) { code_section()->emit_int8(   x); }
 
 void emit_int8 ( int8_t  x)  { *((int8_t*)  end()) = x; set_end(end() + sizeof(int8_t)); }
  1. __ movptr(rcx, parameter_size) 将parameter_size参数地址移动到rcx寄存器。
void MacroAssembler::movptr(Register dst, Address src) {
  LP64_ONLY(movq(dst, src)) NOT_LP64(movl(dst, src));
}
  1. __ shlptr(rcx, Interpreter::logStackElementSize) logStackElementSize在32位平台是2,所以改汇编是将rcx的值向左移动两位, 也就是rcx值乘以4, 以为32平台一个参数地址是4字节,将存储的参数转化成字节数,
  2. __ addptr(rcx, locals_count_in_bytes) 就是rcx的值加上参数的字节数,用来保存具体参数。
  3. __ subptr(rsp, rcx) 将rsp的值减去rcx的值,实际是开辟了保存参数空间。
  4. __ andptr(rsp, -(StackAlignmentInBytes)) 由于StackAlignmentInBytes在x86平台是16,而-16=0xfffffff0,实际就是为了内存对齐,保证rsp地址值是8的倍数.、
  5. __ movptr(saved_rdi, rdi); __ movptr(saved_rsi, rsi); __ movptr(saved_rbx, rbx); 这三条就是保存rdi,rsi,rbx寄存器的值.
  6. 判断sse_save是否大于0,在x86平台是支持UseSSE是99,所以sse_save为true,条件里逻辑就是保存并初始化mxcsr寄存器.
  7. 将parameter_size值赋值给rcx寄存器,并赋值rcx为0,判断parameter_size大于0,如果为0,则跳过参数处理。
  Label parameters_done;\
    // parameter_size大小值赋值给rcx寄存器.
   __ movl(rcx, parameter_size);将parameter值移动rcx寄存器 
    // 检测rcx和它本身做and操作 
   __ testl(rcx, rcx);
   // 如果为0,则跳转到后面__ BIND(parameters_done)位置,即没有参数处理,跳过参数处理 
   __ jcc(Assembler::zero, parameters_done);
  1. 能执行到下面这段,说明删一部判断的参数个数不为0,则处理传递的参数,经过下面参数的地址就一个一个拷贝到rsp的栈顶的位置,但是这里需要注意的点是参数拷贝的顺序是传递的参数是刚好相反拷贝的,此时就是说栈顶是 传入参数的第一个.
     Label loop;
    // 将parameters地址移动到rdx寄存器
   __ movptr(rdx, parameters);
     //将rbx值异或,并将rbx重置为0.
   __ xorptr(rbx, rbx);
     // 绑定loop的标签
   __ BIND(loop); 
       //将以rdx为基础地址,rcx作为索引下标,移动rax寄存器
   __ movptr(rax, Address(rdx, rcx, Interpreter::stackElementScale(), -wordSize));
      // 将rax地址值存储到以rsp为基地址,rbx为索引下标
   __ movptr(Address(rsp, rbx, Interpreter::stackElementScale(),
                   Interpreter::expr_offset_in_bytes(0)), rax);
      //将下标rbx值减1              
   __ increment(rbx);
      //将下标rcx值减1 
   __ decrement(rcx);
    // 如果不等于0,则跳转到 BIND(loop)这一行的位置.
   __ jcc(Assembler::notZero, loop);
  1. 参数处理完成后,那么接下来就是完成java的方法调用
    //获取方法的地址
    __ movptr(rbx, method); 
     //将entry_point地址赋值个rax
    __ movptr(rax, entry_point); 
    //将rsp栈顶地址赋值给rsi寄存器
    __ mov(rsi, rsp); 
    // 调用java方法入口entry_point就是字节码的入口
    __ call(rax);
  1. 获取call_stub的返回地址赋值给return_address。
 return_address = __ pc()
  1. 接下来就是处理返回值。
    //将result的地址赋值给rdi寄存器
    __ movptr(rdi, result);
    //定义is_long,is_float,is_double,exit四个标签
    Label is_long, is_float, is_double, exit;
     //将result_type地址赋值给rsi寄存器
    __ movl(rsi, result_type);
     // 比较rsi的类型与T_LONG比较
    __ cmpl(rsi, T_LONG);
     // 相等则跳转到BIND(is_long)位置
    __ jcc(Assembler::equal, is_long);
     // 比较rsi的类型与T_FLOAT比较
    __ cmpl(rsi, T_FLOAT);
     // 相等则跳转到BIND(is_float)位置
    __ jcc(Assembler::equal, is_float);
     // 比较rsi的类型与T_DOUBLE比较
    __ cmpl(rsi, T_DOUBLE);
      // 相等则跳转到BIND(is_double)位置
    __ jcc(Assembler::equal, is_double);
    
     //省略部分代码
    __ BIND(is_long);
    __ movl(Address(rdi, 0 * wordSize), rax);
    __ movl(Address(rdi, 1 * wordSize), rdx);
    __ jmp(exit);

    __ BIND(is_float);
    // interpreter uses xmm0 for return values
    if (UseSSE >= 1) {
      __ movflt(Address(rdi, 0), xmm0);
    } else {
      __ fstp_s(Address(rdi, 0));
    }
    __ jmp(exit);

    __ BIND(is_double);
    // interpreter uses xmm0 for return values
    if (UseSSE >= 2) {
      __ movdbl(Address(rdi, 0), xmm0);
    } else {
      __ fstp_d(Address(rdi, 0));
    }
    __ jmp(exit);
  1. 走到这一行,说明是返回result_type既不是long、float、double类型, 则处理T_INT类型,rax的函数返回值存放在rdi寄存器的地址中.
     __ movl(Address(rdi, 0), rax)
    
  2. 将rsp_after_call的值赋值给rep寄存器,这样就释放了栈上参数的空间.
    __ lea(rsp, rsp_after_call);
    
  3. 这一步是恢复mxcsr寄存的值
if (sse_save) {
    __ ldmxcsr(mxcsr_save);
  }
  1. 恢复调用前rbx、rsi、rdi的寄存器的值,并rsp寄存器地址加上4个子长的宽度,32位平台就是字长是8,就是32位.让rsp指向保存调用java方法前的保存的rbp的值。
    __ movptr(rbx, saved_rbx);
    __ movptr(rsi, saved_rsi);
    __ movptr(rdi, saved_rdi);
    __ addptr(rsp, 4*wordSize);
  1. pop将栈顶rsp栈顶的值弹出放入rbp寄存器,这样栈帧的rbp栈基地址就回到调用java方法之前的地址了,然后ret指令将栈顶返回地址弹回到指令指针寄存器,这样就从调用java方法返回到调用者的地址,
    __ pop(rbp);
    __ ret(0);
  1. 最后generate_call_stub方法的最后将生成call_stub的入口地址返回,此时call_stub的指令就生成结束,这个start地址就是call_stub的入口地址
 return start;

下面就是call_stub盗用java方法的栈的示意图 image.png 总结
本文主要是就JNIEnv->CallStaticVoidMethod的函数底层实现逻辑,主要是调用jni_invoke_static,然后再调用Javacalls::call,其底层调用call_helper包装 StubRoutines::call_stub实现对函数的调用。