专注虚拟机与编译器研究

第35篇-方法调用指令之invokespecial与invokestatic

这一篇将详细介绍invokespecial和invokestatic字节码指令的汇编实现逻辑

1、invokespecial指令

invokespecial指令的模板定义如下:

def(Bytecodes::_invokespecial       , ubcp|disp|clvm|____, vtos, vtos, invokespecial       , f1_byte      );

生成函数为invokespecial(),生成的汇编代码如下:

0x00007fffe1022250: mov    %r13,-0x38(%rbp)
0x00007fffe1022254: movzwl 0x1(%r13),%edx
0x00007fffe1022259: mov    -0x28(%rbp),%rcx
0x00007fffe102225d: shl    $0x2,%edx
0x00007fffe1022260: mov    0x10(%rcx,%rdx,8),%ebx
// 获取ConstantPoolCacheEntry中indices[b2,b1,constant pool index]中的b1
0x00007fffe1022264: shr    $0x10,%ebx
0x00007fffe1022267: and    $0xff,%ebx
// 检查invokespecial=183的bytecode是否已经连接,如果已经连接就进行跳转
0x00007fffe102226d: cmp    $0xb7,%ebx
0x00007fffe1022273: je     0x00007fffe1022312

// ... 省略调用InterpreterRuntime::resolve_invoke()函数
// 对invokespecial=183的bytecode进行连接,
// 因为字节码指令还没有连接

// 将invokespecial x中的x加载到%edx中
0x00007fffe1022306: movzwl 0x1(%r13),%edx
// 将ConstantPoolCache的首地址存储到%rcx中
0x00007fffe102230b: mov    -0x28(%rbp),%rcx
// %edx中存储的是ConstantPoolCacheEntry项的索引,转换为字偏移
0x00007fffe102230f: shl    $0x2,%edx

// 获取ConstantPoolCache::_f1属性的值
0x00007fffe1022312: mov    0x18(%rcx,%rdx,8),%rbx 
// 获取ConstantPoolCache::_flags属性的值
0x00007fffe1022317: mov    0x28(%rcx,%rdx,8),%edx 


// 将flags移动到ecx中
0x00007fffe102231b: mov    %edx,%ecx
// 从flags中取出参数大小
0x00007fffe102231d: and    $0xff,%ecx
// 获取到recv,%rcx中保存的是参数大小,最终计算为 %rsp+%rcx*8-0x8,
// flags中的参数大小可能对实例方法来说,已经包括了recv的大小
// 如调用实例方法的第一个参数是this(recv)
0x00007fffe1022323: mov    -0x8(%rsp,%rcx,8),%rcx 
// 从flags中获取return type,也就是从_flags的高4位保存的TosState
0x00007fffe1022328: shr    $0x1c,%edx
// 将TemplateInterpreter::invoke_return_entry地址存储到%r10
0x00007fffe102232b: movabs $0x7ffff73b6380,%r10   
// 找到对应return type的invoke_return_entry的地址
0x00007fffe1022335: mov    (%r10,%rdx,8),%rdx     
// 通过invokespecial指令调用函数后的返回地址
0x00007fffe1022339: push   %rdx     
              
// 空值检查
0x00007fffe102233a: cmp    (%rcx),%rax  

// ...

// 设置调用者栈顶
0x00007fffe102235c: lea    0x8(%rsp),%r13
// 向栈中last_sp的位置保存调用者栈顶
0x00007fffe1022361: mov    %r13,-0x10(%rbp)

// 跳转到Method::_from_interpretered_entry入口去执行
0x00007fffe1022365: jmpq   *0x58(%rbx)  

invokespecial指令在调用private和构造方法时,不需要动态分发。在这个字节码指令解析完成后,ConstantPoolCacheEntry中的_f1指向目标方法的Method实例,_f2没有使用,所以如上汇编的逻辑非常简单,这里不再过多介绍。 

2、invokestatic指令 

invokestatic指令的模板定义如下: 

def(Bytecodes::_invokestatic        , ubcp|disp|clvm|____, vtos, vtos, invokestatic        , f1_byte);

生成函数为invokestatic(),生成的汇编代码如下:

0x00007fffe101c030: mov    %r13,-0x38(%rbp)
0x00007fffe101c034: movzwl 0x1(%r13),%edx
0x00007fffe101c039: mov    -0x28(%rbp),%rcx
0x00007fffe101c03d: shl    $0x2,%edx
0x00007fffe101c040: mov    0x10(%rcx,%rdx,8),%ebx
0x00007fffe101c044: shr    $0x10,%ebx
0x00007fffe101c047: and    $0xff,%ebx
0x00007fffe101c04d: cmp    $0xb8,%ebx
// 检查invokestatic=184的bytecode是否已经连接,如果已经连接就进行跳转 
0x00007fffe101c053: je     0x00007fffe101c0f2


// 调用InterpreterRuntime::resolve_invoke()函数对invokestatic=184的
// 的bytecode进行连接,因为字节码指令还没有连接
// ... 省略了解析invokestatic的汇编代码 

// 将invokestatic x中的x加载到%edx中
0x00007fffe101c0e6: movzwl 0x1(%r13),%edx
// 将ConstantPoolCache的首地址存储到%rcx中
0x00007fffe101c0eb: mov    -0x28(%rbp),%rcx
// %edx中存储的是ConstantPoolCacheEntry项的索引,转换为字偏移
0x00007fffe101c0ef: shl    $0x2,%edx


// 获取ConstantPoolCache::_f1属性的值
0x00007fffe101c0f2: mov    0x18(%rcx,%rdx,8),%rbx
// 获取ConstantPoolCache::_flags属性的值
0x00007fffe101c0f7: mov    0x28(%rcx,%rdx,8),%edx


// 从flags中获取return type,也就是从_flags的高4位保存的TosState
0x00007fffe101c0fb: shr    $0x1c,%edx
// 将TemplateInterpreter::invoke_return_entry地址存储到%r10
0x00007fffe101c0fe: movabs $0x7ffff73b5d00,%r10
// 找到对应return type的invoke_return_entry的地址
0x00007fffe101c108: mov    (%r10,%rdx,8),%rdx
// 通过invokespecial指令调用函数后的返回地址
0x00007fffe101c10c: push   %rdx


// 设置调用者栈顶
0x00007fffe101c10d: lea    0x8(%rsp),%r13
// 向栈中last_sp的位置保存调用者栈顶
0x00007fffe101c112: mov    %r13,-0x10(%rbp)

// 跳转到Method::_from_interpretered_entry入口去执行
0x00007fffe101c116: jmpq   *0x58(%rbx)  

invokespecial指令在调用静态方法时,不需要动态分发。在这个字节码指令解析完成后,ConstantPoolCacheEntry中的_f1指向目标方法的Method实例,_f2没有使用,所以如上汇编的逻辑非常简单,这里不再过多介绍。 

关于invokestatic与invokespecial的解析过程这里就不再过多介绍了,有兴趣的可从LinkResolver::resolve_invoke()函数查看具体的解析过程。 

推荐阅读:

第1篇-关于JVM运行时,开篇说的简单些

第2篇-JVM虚拟机这样来调用Java主类的main()方法

第3篇-CallStub新栈帧的创建

第4篇-JVM终于开始调用Java主类的main()方法啦

第5篇-调用Java方法后弹出栈帧及处理返回结果

第6篇-Java方法新栈帧的创建

第7篇-为Java方法创建栈帧

第8篇-dispatch_next()函数分派字节码

第9篇-字节码指令的定义

第10篇-初始化模板表

第11篇-认识Stub与StubQueue

第12篇-认识CodeletMark

第13篇-通过InterpreterCodelet存储机器指令片段

第14篇-生成重要的例程

第15章-解释器及解释器生成器

第16章-虚拟机中的汇编器

第17章-x86-64寄存器

第18章-x86指令集之常用指令

第19篇-加载与存储指令(1)

第20篇-加载与存储指令之ldc与_fast_aldc指令(2)

第21篇-加载与存储指令之iload、_fast_iload等(3)

第22篇-虚拟机字节码之运算指令

第23篇-虚拟机字节码指令之类型转换

第24篇-虚拟机对象操作指令之getstatic

第25篇-虚拟机对象操作指令之getfield

第26篇-虚拟机对象操作指令之putstatic

第27篇-虚拟机字节码指令之操作数栈管理指令

第28篇-虚拟机字节码指令之控制转移指令

第29篇-调用Java主类的main()方法

第30篇-main()方法的执行

第31篇-方法调用指令之invokevirtual

第32篇-解析interfacevirtual字节码指令

第33篇-方法调用指令之invokeinterface

第34篇-解析invokeinterface字节码指令

  

 

posted on 2021-11-02 09:47  鸠摩(马智)  阅读(784)  评论(0编辑  收藏  举报

导航