JVM函数调用机制
JVM要实现直接由C语言直接调用机器指令,通过两种方式。
第一、C语言内嵌汇编,内嵌汇编只能实现C语言直接调用汇编指令,而不是机器指令,机器指令和汇编指令还是有很大差距,例:(MOV AX,1234H 对应的机器码为:B83412)。
第二、函数指针,通过函数指针,C语言可以将一个变量指向一个函数的首地址,C语言被编译时,C函数直接被编译成为机器指令,而这个函数指针将直接指向这段机器指令的首地址。
1、JVM内部通过函数指针进行函数调用。
函数指针
int (*addPointer) (int a,int b),可以将其指向一个入参为两个int的函数。
1.1CallStub函数指针
1.1.1_call_stub_entry例程
1、pc()函数,保存当前例程所对应的一段机器码的起始位置。
2、generate_all_stub(),得到调用者函数和被调用者函数的参数关系,通过相对位置寻址。
3、CallStub:保存调用者堆栈,保存调用者函数的栈基地址,重新定义栈基地址。
4、CallStub:动态分配堆栈。JVM为了能够调用java函数,需要在运行期知道一个java函数的入参大小,然后动态的计算出所需要的堆栈空间。JVM通过堆栈的“寄生”机制,扩展别人的堆栈,存储自己所需要的数据。call_helper()并没有直接将java函数的入参传递给CallStub(),因为这个调用者并不直接是java函数自己,而是C函数调用java函数,因此在jvm的编译阶段,并没有将java函数压栈,jvm内部也抽象了面向对象的思想,所以传递给call_helper()函数的入参不是函数的实际入参,而是实际入参的引用,即指针。指针大小一样,所以通过计算指针大小与parameter_size就可以得到实际的入参所占用的堆栈空间=java函数入参数量4+44,每个指针4个字节,44则是保存调用者所执行到的java程序所对应的机器指令的基址和变址。
5、CallStub:调用者保存,这里的调用者保存与保存调用者堆栈要能够区分开来,保存调用者堆栈是保存栈基地址,而调用者保存是保存的方法内部的私有数据地址,比如长度为10的数组,找到第五个元素的时候调用另外一个函数,那么这个数组当前的位置就得保存到寄存器中。调用者保存主要是针对edi、esi与ebx寄存器而言。
6、CallStub:参数压栈。动态分配堆栈的时候说过,CallStub为被调用者分配的堆栈空间大小=java函数入参数量4+44,这里的44指的是rdi、rsi、tbx、mxcsr这四个寄存器的位置。而java函数的入参主要通过入参数量与存储入参寄存器的首地址进行寻找,由于每个入参都是指针,所以知道入参数量便能拿到所有的入参。java函数入参通过循环写入到CallStub函数中。
7、调用entry_point例程,这个将在内存模型了解清楚后进行详解。例程就是一段预先写好的函数,jvm通过例程函数在启动过程中生成机器指令,当执行java函数调用时,jvm直接跳转到例程所生成的这段机器指令去执行。
8、CallStub:获得返回值.entry_point例程之后,会有返回值,CallStub会获取返回值进行处理。
本章总结
物理机器执行函数调用——>用函数指针实现C/C++程序中直接执行本地机器指令——>call_stub例程的学习。