4

【JVM源码解析】虚拟机解释执行Java方法(下)

 2 years ago
source link: https://segmentfault.com/a/1190000041097512
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

本文由HeapDump性能社区首席讲师鸠摩(马智)授权整理发布

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

与invokevirtual指令类似,当没有对目标方法进行解析时,需要调用LinkResolver::resolve_invoke()函数进行解析,这个函数会调用其它一些函数完成方法的解析,如下图所示。

上图中粉色的部分与解析invokevirtual字节码指令有所区别,resolve_pool()函数及其调用的相关函数在介绍invokevirtual字节码指令时详细介绍过,这里不再介绍。

调用LinkResolver::resolve_invokeinterface()函数对字节码指令进行解析。函数的实现如下:

void LinkResolver::resolve_invokeinterface(
 CallInfo& result,
 Handle recv,
 constantPoolHandle pool,
 int index, // 指的是常量池缓存项的索引
 TRAPS
) {
  KlassHandle resolved_klass;
  Symbol* method_name = NULL;
  Symbol* method_signature = NULL;
  KlassHandle current_klass;
  // 解析常量池时,传入的参数pool(根据当前栈中要执行的方法找到对应的常量池)和
  // index(常量池缓存项的缓存,还需要映射为原常量池索引)是有值的,根据这两个值能够
  // 解析出resolved_klass和要查找的方法名称method_name和方法签名method_signature
  resolve_pool(resolved_klass, method_name, method_signature, current_klass, pool, index, CHECK);

  KlassHandle recvrKlass (THREAD, recv.is_null() ? (Klass*)NULL : recv->klass());
  resolve_interface_call(result, recv, recvrKlass, resolved_klass, method_name, method_signature, current_klass, true, true, CHECK);
}

我们接着看resolve\_interface\_call()函数的实现,如下:

void LinkResolver::resolve_interface_call(
 CallInfo& result,
 Handle recv,
 KlassHandle recv_klass,
 KlassHandle resolved_klass,
 Symbol* method_name,
 Symbol* method_signature,
 KlassHandle current_klass,
 bool             check_access,
 bool            check_null_and_abstract,
 TRAPS
) {
  methodHandle resolved_method;
  linktime_resolve_interface_method(resolved_method, resolved_klass, method_name, method_signature, current_klass, check_access, CHECK);
  runtime_resolve_interface_method(result, resolved_method, resolved_klass, recv, recv_klass, check_null_and_abstract, CHECK);
}

调用2个函数对方法进行解析。首先看linktime\_resolve\_interface_method()函数的实现。

调用linktime\_resolve\_interface\_method()函数会调用LinkResolver::resolve\_interface_method()函数,此函数的实现如下:

void LinkResolver::resolve_interface_method(
 methodHandle& resolved_method,
 KlassHandle resolved_klass,
 Symbol* method_name,
 Symbol* method_signature,
 KlassHandle current_klass,
 bool          check_access,
 bool          nostatics,
 TRAPS
) {
  // 从接口和父类java.lang.Object中查找方法,包括静态方法
  lookup_method_in_klasses(resolved_method, resolved_klass, method_name, method_signature, false, true, CHECK);

  if (resolved_method.is_null()) {
    // 从实现的所有接口中查找方法
    lookup_method_in_interfaces(resolved_method, resolved_klass, method_name, method_signature, CHECK);
    if (resolved_method.is_null()) {
      // no method found
      // ...
    }
  }

  // ...
}

首先调用LinkResolver::lookup\_method\_in_klasses()函数进行方法查找,在之前介绍过invokevirtual字节码指令时介绍过这个函数,不过只介绍了与invokevirtual指令相关的处理逻辑,这里需要继续查看invokeinterface的相关处理逻辑,实现如下: 

void LinkResolver::lookup_method_in_klasses(
 methodHandle& result,
 KlassHandle klass,
 Symbol* name,
 Symbol* signature,
 bool checkpolymorphism,
 // 对于invokevirtual来说,值为false,对于invokeinterface来说,值为true
 bool in_imethod_resolve,
 TRAPS
) {
  Method* result_oop = klass->uncached_lookup_method(name, signature);

  // 在接口中定义方法的解析过程中,忽略Object类中的静态和非public方法,如
  // clone、finalize、registerNatives
  if (
      in_imethod_resolve &&
      result_oop != NULL &&
      klass->is_interface() &&
      (result_oop->is_static() || !result_oop->is_public()) &&
      result_oop->method_holder() == SystemDictionary::Object_klass() // 方法定义在Object类中
  ) {
    result_oop = NULL;
  }

  if (result_oop == NULL) {
    Array<Method*>* default_methods = InstanceKlass::cast(klass())->default_methods();
    if (default_methods != NULL) {
      result_oop = InstanceKlass::find_method(default_methods, name, signature);
    }
  }
  // ...
  result = methodHandle(THREAD, result_oop);
}

调用uncached\_lookup\_method()函数从当前类和父类中查找,如果没有找到或找到的是Object类中的不合法方法,则会调用find_method()函数从默认方法中查找。在Java8的新特性中有一个新特性为接口默认方法,该新特性允许我们在接口中添加一个非抽象的方法实现,而这样做的方法只需要使用关键字default修饰该默认实现方法即可。

uncached\_lookup\_method()函数的实现如下:

Method* InstanceKlass::uncached_lookup_method(Symbol* name, Symbol* signature) const {
  Klass* klass = const_cast<InstanceKlass*>(this);
  bool dont_ignore_overpasses = true; 
  while (klass != NULL) {
    Method* method = InstanceKlass::cast(klass)->find_method(name, signature);
    if ((method != NULL) && (dont_ignore_overpasses || !method->is_overpass())) {
      return method;
    }
    klass = InstanceKlass::cast(klass)->super();
    dont_ignore_overpasses = false; // 不要搜索父类中的overpass方法
  }
  return NULL;
}

从当前类和父类中查找方法。当从类和父类中查找方法时,调用find\_method()函数,最终调用另外一个重载函数find\_method()从InstanceKlass::\_methods属性中保存的方法中进行查找;当从默认方法中查找方法时,调用find\_method()函数从InstanceKlass::\_default\_methods属性中保存的方法中查找。重载的find_method()函数的实现如下:

Method* InstanceKlass::find_method(Array<Method*>* methods, Symbol* name, Symbol* signature) {
  int hit = find_method_index(methods, name, signature);
  return hit >= 0 ? methods->at(hit): NULL;
}

其实调用find\_method\_index()函数就是根据二分查找来找名称为name,签名为signature的方法,因为InstanceKlass::\_methods和InstanceKlass::\_default_methods属性中的方法已经进行了排序,关于这些函数中存储的方法及如何进行排序在《深入剖析Java虚拟机:源码剖析与实例详解(基础卷)》一书中详细介绍过,这里不再介绍。

调用的LinkResolver::runtime\_resolve\_interface_method()函数的实现如下:

void LinkResolver::runtime_resolve_interface_method(
 CallInfo& result,
 methodHandle resolved_method,
 KlassHandle resolved_klass,
 Handle recv,
 KlassHandle recv_klass,
 bool check_null_and_abstract, // 对于invokeinterface来说,值为false
 TRAPS
) {
  // ...

  methodHandle sel_method;

  lookup_instance_method_in_klasses(
            sel_method, 
            recv_klass,
            resolved_method->name(),
            resolved_method->signature(), 
            CHECK);

  if (sel_method.is_null() && !check_null_and_abstract) {
    sel_method = resolved_method;
  }

  // ...
  // 如果查找接口的实现时找到的是Object类中的方法,那么要通过vtable进行分派,所以我们需要
  // 更新的是vtable相关的信息
  if (!resolved_method->has_itable_index()) {
    int vtable_index = resolved_method->vtable_index();
    assert(vtable_index == sel_method->vtable_index(), "sanity check");
    result.set_virtual(resolved_klass, recv_klass, resolved_method, sel_method, vtable_index, CHECK);
  } else {
    int itable_index = resolved_method()->itable_index();
    result.set_interface(resolved_klass, recv_klass, resolved_method, sel_method, itable_index, CHECK);
  }
}

当没有itable索引时,通过vtable进行动态分派;否则通过itable进行动态分派。 

调用的lookup\_instance\_method\_in\_klasses()函数的实现如下:

void LinkResolver::lookup_instance_method_in_klasses(
 methodHandle& result,
 KlassHandle klass,
 Symbol* name,
 Symbol* signature,
 TRAPS
) {
  Method* result_oop = klass->uncached_lookup_method(name, signature);
  result = methodHandle(THREAD, result_oop);
  // 循环查找方法的实现,不会查找静态方法
  while (!result.is_null() && result->is_static() && result->method_holder()->super() != NULL) {
    KlassHandle super_klass = KlassHandle(THREAD, result->method_holder()->super());
    result = methodHandle(THREAD, super_klass->uncached_lookup_method(name, signature));
  }

  // 当从拥有Itable的类或父类中找到接口中方法的实现时,result不为NULL,
  // 否则为NULL,这时候就要查找默认的方法实现了,这也算是一种实现
  if (result.is_null()) {
    Array<Method*>* default_methods = InstanceKlass::cast(klass())->default_methods();
    if (default_methods != NULL) {
      result = methodHandle(InstanceKlass::find_method(default_methods, name, signature));
    }
  }
}

如上在查找默认方法实现时会调用find_method()函数,此函数在之前介绍invokevirtual字节码指令的解析过程时详细介绍过,这里不再介绍。

在LinkResolver::runtime\_resolve\_interface\_method()函数的最后有可能调用CallInfo::set\_interface()或CallInfo::set\_virtual()函数,调用这两个函数就是将查找到的信息保存到CallInfo实例中。最终会在InterpreterRuntime::resolve\_invoke()函数中根据CallInfo实例中保存的信息更新ConstantPoolCacheEntry相关的信息,如下:

switch (info.call_kind()) {
  // ...
  case CallInfo::itable_call:
    cache_entry(thread)->set_itable_call(
      bytecode,
      info.resolved_method(),
      info.itable_index());
    break;
  default: ShouldNotReachHere();
}

当CallInfo中保存的是itable的分派信息时,调用set\_itable\_call()函数,这个函数的实现如下:

void ConstantPoolCacheEntry::set_itable_call(
 Bytecodes::Code invoke_code,
 methodHandle method,
 int index
) {
  assert(invoke_code == Bytecodes::_invokeinterface, "");
  InstanceKlass* interf = method->method_holder();
  // interf一定是接口,而method一定是非final方法
  set_f1(interf); // 对于itable,_f1保存的是表示接口的InstanceKlass
  set_f2(index); // 对于itable,_f2保存的是itable索引
  set_method_flags(as_TosState(method->result_type()),
                   0, // no option bits
                   method()->size_of_parameters());
  set_bytecode_1(Bytecodes::_invokeinterface);
}

使用CallInfo实例中的信息更新ConstantPoolCacheEntry中的信息即可。

第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()函数查看具体的解析过程。

第36篇-方法返回指令之return

方法返回的字节码相关指令如下表所示。 
image.png

模板定义如下:

def(Bytecodes::_ireturn , ____|disp|clvm|____, itos, itos, _return , itos );
def(Bytecodes::_lreturn , ____|disp|clvm|____, ltos, ltos, _return , ltos );
def(Bytecodes::_freturn , ____|disp|clvm|____, ftos, ftos, _return , ftos );
def(Bytecodes::_dreturn , ____|disp|clvm|____, dtos, dtos, _return , dtos );
def(Bytecodes::_areturn , ____|disp|clvm|____, atos, atos, _return , atos );
def(Bytecodes::_return , ____|disp|clvm|____, vtos, vtos, _return , vtos );

def(Bytecodes::_return_register_finalizer , ____|disp|clvm|____, vtos, vtos, _return , vtos );

生成函数都为TemplateTable::\_return()。但是如果是Object对象的构造方法中的return指令,那么这个指令还可能会被重写为\_return\_register\_finalizer指令。

生成的return字节码指令对应的汇编代码如下: 

// 将JavaThread::do_not_unlock_if_synchronized属性存储到%dl中
0x00007fffe101b770: mov 0x2ad(%r15),%dl
// 重置JavaThread::do_not_unlock_if_synchronized属性值为false
0x00007fffe101b777: movb $0x0,0x2ad(%r15)

// 将Method*加载到%rbx中
0x00007fffe101b77f: mov -0x18(%rbp),%rbx
// 将Method::_access_flags加载到%ecx中
0x00007fffe101b783: mov 0x28(%rbx),%ecx
// 检查Method::flags是否包含JVM_ACC_SYNCHRONIZED
0x00007fffe101b786: test $0x20,%ecx
// 如果方法不是同步方法,跳转到----unlocked----
0x00007fffe101b78c: je 0x00007fffe101b970


// 如果在%dl寄存器中存储的_do_not_unlock_if_synchronized的值不为0,
// 则跳转到no_unlock,表示不要释放和锁相关的资源 
0x00007fffe101b792: test $0xff,%dl 
0x00007fffe101b795: jne 
0x00007fffe101ba90 // 跳转到----no_unlock----处

在JavaThread类中定义了一个属性\_do\_not\_unlock\_if\_synchronized,这个值表示在抛出异常的情况下不要释放receiver(在非静态方法调用的情况下,我们总是会将方法解析到某个对象上,这个对象就是这里的receiver,也可称为接收者),此值仅在解释执行的情况下才会起作用。初始的时候会初始化为false。在如上汇编中可以看到,当\_do\_not\_unlock\_if\_synchronized的值为true时,表示不需要释放receiver,所以虽然当前是同步方法,但是却直接调用到了no_unlock处。

如果执行如下汇编代码,则表示%dl寄存器中存储的\_do\_not\_unlock\_if_synchronized的值为0,需要执行释放锁的操作。

// 将之前字节码指令执行的结果存储到表达式栈顶,
// 由于return不需要返回执行结果,所以不需要设置返回值等信息,
// 最终在这里没有生成任何push指令

// 将BasicObjectLock存储到%rsi中,由于%rsi在调用C++函数时可做为
// 第2个参数传递,所以如果要调用unlock_object就可以传递此值
0x00007fffe101b79b: lea -0x50(%rbp),%rsi

// 获取BasicObjectLock::obj属性地址存储到%rax中
0x00007fffe101b79f: mov 0x8(%rsi),%rax 

// 如果不为0,则跳转到unlock处,因为不为0,表示
// 这个obj有指向的锁对象,需要进行释放锁的操作
0x00007fffe101b7a3: test %rax,%rax
0x00007fffe101b7a6: jne 0x00007fffe101b8a8 // 跳转到----unlock----处

// 如果是其它的return指令,则由于之前通过push指令将结果保存在
// 表达式栈上,所以现在可通过pop将表达式栈上的结果弹出到对应寄存器中

第1个指令的-0x50(%rbp)指向了第1个BasicObjectLock对象,其中的sizeof(BasicObjectLock)的值为16,也就是16个字节。在之前我们介绍栈帧的时候介绍过Java解释栈的结构,如下:

假设当前的栈帧中有2个锁对象,则会在栈帧中存储2个BasicObjectLock对象,BasicObjectLock中有2个属性,\_lock和\_obj,分别占用8字节。布局如下图所示。

由于return字节码指令负责要释放的是加synchronized关键字的、解释执行的Java方法,所以为synchronized关键字建立的第1个锁对象存储在离当前栈帧最靠近栈底的地方,也就是上图中灰色部分,而其它锁对象我们暂时不用管。灰色部分表示的BasicObjectLock的地址通过-0x50(%rbp)就能获取到,然后对其中的\_lock和\_obj属性进行操作。

由于现在还没有介绍锁相关的知识,所以这里不做过多介绍,在后面介绍完锁相关知识后还会详细介绍。 

在变量throw\_monitor\_exception为true的情况下,通过调用call\_VM()函数生成抛出锁状态异常的汇编代码,这些汇编代码主要是为了执行C++函数InterpreterRuntime::throw\_illegal\_monitor\_state\_exception()。完成执行后还会执行由should\_not\_reach\_here()函数生成的汇编代码。

在变量throw\_monitor\_exception为false并且install\_monitor\_exception为true的情况下,通过调用call\_VM()函数生成汇编代码来执行C++函数InterpreterRuntime::new\_illegal\_monitor\_state_exception()。最后跳转到unlocked处执行。

在InterpreterMacroAssembler::remove\_activation()函数中,bind完unlock后就会调用InterpreterMacroAssembler::unlock\_object()函数生成如下的汇编代码。InterpreterMacroAssembler::unlock_object()函数的作用如下:

Unlocks an object. Used in monitorexit bytecode and remove_activation. Throws an IllegalMonitorException if object is not locked by current thread.

生成的汇编代码如下:

// **** unlock ****

// ============调用InterpreterMacroAssembler::unlock_object()函数生成如下的汇编代码==================

// 将%r13存储到栈中,防止异常破坏了%r13寄存器中的值
0x00007fffe101b8a8: mov %r13,-0x38(%rbp)

// 将BasicObjectLock::_lock的地址存储到%rax寄存器中
0x00007fffe101b8ac: lea (%rsi),%rax
// 将BasicObjectLock::_obj存储到%rcx寄存器中
0x00007fffe101b8af: mov 0x8(%rsi),%rcx

// 将BasicObjectLock::_obj的值设置为NULL,表示释放锁操作
0x00007fffe101b8b3: movq $0x0,0x8(%rsi)

// ----------当UseBiasedLocking的值为true时,调用MacroAssembler::biased_locking_exit()生成如下的汇编代码------------
// 从BasicObjectLock::_obj对象中取出mark属性值并相与
0x00007fffe101b8bb: mov (%rcx),%rdx
0x00007fffe101b8be: and $0x7,%rdx
// 如果BasicObjectLock::_obj指向的oop的mark属性后3位是偏向锁的状态,则跳转到---- done ----
0x00007fffe101b8c2: cmp $0x5,%rdx
0x00007fffe101b8c6: je 0x00007fffe101b96c
// ------------------------结束调用MacroAssembler::biased_locking_exit()生成的汇编代码---------------------

// 将BasicObjectLock::_lock这个oop对象的_displaced_header属性值取出
0x00007fffe101b8cc: mov (%rax),%rdx
// 判断一下是否为锁的重入,如果是锁的重入,则跳转到---- done ----
0x00007fffe101b8cf: test %rdx,%rdx
0x00007fffe101b8d2: je 0x00007fffe101b96c

// 让BasicObjectLock::_obj的那个oop的mark恢复为
// BasicObjectLock::_lock中保存的原对象头
0x00007fffe101b8d8: lock cmpxchg %rdx,(%rcx)
// 如果为0,则表示锁的重入,跳转到---- done ---- ????
0x00007fffe101b8dd: je 0x00007fffe101b96c

// 让BasicObjectLock::_obj指向oop,这个oop的对象头已经替换为了BasicObjectLock::_lock中保存的对象头
0x00007fffe101b8e3: mov %rcx,0x8(%rsi)

// -----------调用call_VM()函数生成汇编代码来执行C++函数InterpreterRuntime::monitorexit()----------------
0x00007fffe101b8e7: callq 0x00007fffe101b8f1
0x00007fffe101b8ec: jmpq 0x00007fffe101b96c
0x00007fffe101b8f1: lea 0x8(%rsp),%rax
0x00007fffe101b8f6: mov %r13,-0x38(%rbp)
0x00007fffe101b8fa: mov %r15,%rdi
0x00007fffe101b8fd: mov %rbp,0x200(%r15)
0x00007fffe101b904: mov %rax,0x1f0(%r15)
0x00007fffe101b90b: test $0xf,%esp
0x00007fffe101b911: je 0x00007fffe101b929
0x00007fffe101b917: sub $0x8,%rsp
0x00007fffe101b91b: callq 0x00007ffff66b3d22
0x00007fffe101b920: add $0x8,%rsp
0x00007fffe101b924: jmpq 0x00007fffe101b92e
0x00007fffe101b929: callq 0x00007ffff66b3d22
0x00007fffe101b92e: movabs $0x0,%r10
0x00007fffe101b938: mov %r10,0x1f0(%r15)
0x00007fffe101b93f: movabs $0x0,%r10
0x00007fffe101b949: mov %r10,0x200(%r15)
0x00007fffe101b950: cmpq $0x0,0x8(%r15)
0x00007fffe101b958: je 0x00007fffe101b963
0x00007fffe101b95e: jmpq 0x00007fffe1000420
0x00007fffe101b963: mov -0x38(%rbp),%r13
0x00007fffe101b967: mov -0x30(%rbp),%r14
0x00007fffe101b96b: retq 
// ------------------------结束call_VM()函数调用生成的汇编代码--------------------------------

// **** done ****

0x00007fffe101b96c: mov -0x38(%rbp),%r13
0x00007fffe101b970: mov -0x40(%rbp),%rsi

// ==========结束调用InterpreterMacroAssembler::unlock_object()函数生成如下的汇编代码============
// 如果是其它的return指令,则由于之前通过push指令将结果保存在
// 表达式栈上,所以现在可通过pop将表达式栈上的结果弹出到对应寄存器中


// **** unlocked ****
// 在执行这里的代码时,表示当前的栈中没有相关的锁,也就是
// 相关的锁对象已经全部释放

// **** restart ****
// 检查一下,是否所有的锁都已经释放了

// %rsi指向当前栈中最靠栈顶的BasicObjectLock
0x00007fffe101b970: mov -0x40(%rbp),%rsi
// %rbx指向当前栈中最靠栈底的BasicObjectLock
0x00007fffe101b974: lea -0x40(%rbp),%rbx

// 跳转到----entry----
0x00007fffe101b978: jmpq 0x00007fffe101ba8b

执行如下代码,会通过调用call\_VM()函数来生成调用InterpreterRuntime::throw\_illegal\_monitor\_state_exception()函数的代码:

// **** exception ****
// Entry already locked, need to throw exception

// 当throw_monitor_exception的值为true时,执行如下2个函数生成的汇编代码:
// 执行call_VM()函数生成的汇编代码,就是调用C++函数InterpreterRuntime::throw_illegal_monitor_state_exception()
// 执行should_not_reach_here()函数生成的汇编代码 

// 当throw_monitor_exception的值为false,执行如下汇编:
// 执行调用InterpreterMacroAssembler::unlock_object()函数生成的汇编代码
// install_monitor_exception的值为true时,执行call_VM()函数生成的汇编代码,就是调用C++函数InterpreterRuntime::new_illegal_monitor_state_exception() 
// 无条件跳转到----restart ----
// **** loop ****

// 将BasicObjectLock::obj与NULL比较,如果不相等,则跳转到----exception----
0x00007fffe101ba79: cmpq $0x0,0x8(%rsi)
0x00007fffe101ba81: jne 0x00007fffe101b97d // 则跳转到----exception----
// **** entry ****

// 0x10为BasicObjectLock,找到下一个BasicObjectLock
0x00007fffe101ba87: add $0x10,%rsi 
// 检查是否到达了锁对象存储区域的底部
0x00007fffe101ba8b: cmp %rbx,%rsi
// 如果不相等,跳转到loop
0x00007fffe101ba8e: jne 0x00007fffe101ba79 // 跳转到----loop----
// **** no_unlock ****

// 省略jvmti support
 
// 将-0x8(%rbp)处保存的old stack pointer(saved rsp)取出来放到%rbx中
0x00007fffe101bac7: mov -0x8(%rbp),%rbx

// 移除栈帧
// leave指令相当于:
// mov %rbp, %rsp
// pop %rbp
0x00007fffe101bacb: leaveq 
// 将返回地址弹出到%r13中
0x00007fffe101bacc: pop %r13
// 设置%rsp为调用者的栈顶值
0x00007fffe101bace: mov %rbx,%rsp
0x00007fffe101bad1: jmpq *%r13

其中的解释方法返回地址为return address,由于当前是C++函数调用Java,所以这个返回地址其实是C++函数的返回地址,我们不需要考虑。

整个的调用转换如下图所示。

其中的红色部分表示终结这个流程。 

在return字节码指令中会涉及到锁释放的流程,所以上面的流程图看起来会复杂一些,等我们介绍完锁相关知识后会再次介绍return指令,这里不再过多介绍。

第37篇-恢复调用者栈帧例程Interpreter::\_invoke\_return_entry

我们在之前介绍过return字节码指令的执行逻辑,这个字节码指令只会执行释放锁和退出当前栈帧的操作,但是当控制权转移给调用者时,还需要恢复调用者的栈帧状态,如让%r13指向bcp、%r14指向局部变量表等,另外还需要弹出压入的实参、跳转到调用者的下一个字节码指令继续执行,而这一切操作都是由Interpreter::\_return\_entry例程负责的。这个例程在之前介绍invokevirtual和invokeinterface等字节码指令时介绍过,当使用这些字节码指令调用方法时,会根据方法的返回类型压入Interpreter::\_return\_entry一维数组中保存的对应例程地址,这样return字节码指令执行完成后就会执行这段例程。

在invokevirtual和invokeinterface等字节码指令中通过调用如下函数获取对应的例程入口:

address* TemplateInterpreter::invoke_return_entry_table_for(Bytecodes::Code code) {
  switch (code) {
  case Bytecodes::_invokestatic:
  case Bytecodes::_invokespecial:
  case Bytecodes::_invokevirtual:
  case Bytecodes::_invokehandle:
    return Interpreter::invoke_return_entry_table();
  case Bytecodes::_invokeinterface:
    return Interpreter::invokeinterface_return_entry_table();
  default:
    fatal(err_msg("invalid bytecode: %s", Bytecodes::name(code)));
    return NULL;
  }
}

可以看到invokeinterface字节码从Interpreter::\_invokeinterface\_return\_entry数组中获取对应的例程,而其它的从Interpreter::\_invoke\_return\_entry一维数组中获取。如下:

address TemplateInterpreter::_invoke_return_entry[TemplateInterpreter::number_of_return_addrs];
address TemplateInterpreter::_invokeinterface_return_entry[TemplateInterpreter::number_of_return_addrs];

当返回一维数组后,会根据方法返回类型进一步确定例程入口地址。下面我们就看一下这些例程的生成过程。 

TemplateInterpreterGenerator::generate\_all()函数中会生成Interpreter::\_return_entry入口,如下:

{
    CodeletMark cm(_masm, "invoke return entry points");
    const TosState states[] = {itos, itos, itos, itos, ltos, ftos, dtos, atos, vtos};
    const int invoke_length = Bytecodes::length_for(Bytecodes::_invokestatic); // invoke_length=3
    const int invokeinterface_length = Bytecodes::length_for(Bytecodes::_invokeinterface); // invokeinterface=5
 
    for (int i = 0; i < Interpreter::number_of_return_addrs; i++) { // number_of_return_addrs = 9
       TosState state = states[i]; // TosState是枚举类型
       Interpreter::_invoke_return_entry[i] = generate_return_entry_for(state, invoke_length, sizeof(u2)); 
       Interpreter::_invokeinterface_return_entry[i] = generate_return_entry_for(state, invokeinterface_length, sizeof(u2));
    }
}

除invokedynamic字节码指令外,其它的方法调用指令在解释执行完成后都需要调用由generate\_return\_entry\_for()函数生成的例程,生成例程的generate\_return\_entry\_for()函数实现如下:

address TemplateInterpreterGenerator::generate_return_entry_for(TosState state, int step, size_t index_size) {
 
  // Restore stack bottom in case万一 i2c adjusted stack
  __ movptr(rsp, Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize)); // interpreter_frame_last_sp_offset=-2
  // and NULL it as marker that esp is now tos until next java call
  __ movptr(Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize), (int32_t)NULL_WORD);
 
  __ restore_bcp();
  __ restore_locals();
 
  // ...
 
  const Register cache = rbx;
  const Register index = rcx;
  __ get_cache_and_index_at_bcp(cache, index, 1, index_size);
 
  const Register flags = cache;
  __ movl(flags, Address(cache, index, Address::times_ptr, ConstantPoolCache::base_offset() + ConstantPoolCacheEntry::flags_offset()));
  __ andl(flags, ConstantPoolCacheEntry::parameter_size_mask);
  __ lea(rsp, Address(rsp, flags, Interpreter::stackElementScale()) ); // 栈元素标量为8
  __ dispatch_next(state, step);
 
  return entry;
}

根据state的不同(方法的返回类型的不同),会在选择执行调用者方法的下一个字节码指令时,决定要从字节码指令的哪个入口处开始执行。我们看一下,当传递的state为itos(也就是当方法的返回类型为int时)时生成的汇编代码如下:

// 将-0x10(%rbp)存储到%rsp后,置空-0x10(%rbp)
0x00007fffe1006ce0: mov -0x10(%rbp),%rsp // 更改rsp
0x00007fffe1006ce4: movq $0x0,-0x10(%rbp) // 更改栈中特定位置的值
// 恢复bcp和locals,使%r14指向本地变量表,%r13指向bcp
0x00007fffe1006cec: mov -0x38(%rbp),%r13
0x00007fffe1006cf0: mov -0x30(%rbp),%r14
 // 获取ConstantPoolCacheEntry的索引并加载到%ecx
0x00007fffe1006cf4: movzwl 0x1(%r13),%ecx 
 
 // 获取栈中-0x28(%rbp)的ConstantPoolCache并加载到%ecx
0x00007fffe1006cf9: mov -0x28(%rbp),%rbx 
// shl是逻辑左移,获取字偏移
0x00007fffe1006cfd: shl $0x2,%ecx 
// 获取ConstantPoolCacheEntry中的_flags属性值
0x00007fffe1006d00: mov 0x28(%rbx,%rcx,8),%ebx
// 获取_flags中的低8位中保存的参数大小
0x00007fffe1006d04: and $0xff,%ebx 
 
// lea指令将地址加载到内存寄存器中,也就是恢复调用方法之前栈的样子
0x00007fffe1006d0a: lea (%rsp,%rbx,8),%rsp 
 
// 跳转到下一指令执行
0x00007fffe1006d0e: movzbl 0x3(%r13),%ebx 
0x00007fffe1006d13: add $0x3,%r13
0x00007fffe1006d17: movabs $0x7ffff73b7ca0,%r10
0x00007fffe1006d21: jmpq *(%r10,%rbx,8)

如上汇编代码的逻辑非常简单,这里不再过多介绍。  

第38篇-解释方法之间的调用小实例

这一篇我们介绍一下解释执行的main()方法调用解析执行的add()方法的小实例,这个例子如下:

package com.classloading;

public class TestInvokeMethod {
  public int add(int a, int b) {
    return a + b;
  }

  public static void main(String[] args) {
    TestInvokeMethod tim = new TestInvokeMethod();
    tim.add(2, 3);
  }
}

通过Javac编译器编译为字节码文件,如下: 

Constant pool:
   #1 = Methodref #5.#16 // java/lang/Object."<init>":()V
   #2 = Class #17 // com/classloading/TestInvokeMethod
   #3 = Methodref #2.#16 // com/classloading/TestInvokeMethod."<init>":()V
   #4 = Methodref #2.#18 // com/classloading/TestInvokeMethod.add:(II)I
   #5 = Class #19 // java/lang/Object
   #6 = Utf8 <init>
   #7 = Utf8 ()V
   #8 = Utf8 Code
   #9 = Utf8 LineNumberTable
  #10 = Utf8 add
  #11 = Utf8 (II)I
  #12 = Utf8 main
  #13 = Utf8 ([Ljava/lang/String;)V
  #14 = Utf8 SourceFile
  #15 = Utf8 TestInvokeMethod.java
  #16 = NameAndType #6:#7 // "<init>":()V
  #17 = Utf8 com/classloading/TestInvokeMethod
  #18 = NameAndType #10:#11 // add:(II)I
  #19 = Utf8 java/lang/Object
{
  public com.classloading.TestInvokeMethod();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1 // Method java/lang/Object."<init>":()V
         4: return

  public int add(int, int);
    descriptor: (II)I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: iload_1
         1: iload_2
         2: iadd
         3: ireturn

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=2, args_size=1
         0: new #2 // class com/classloading/TestInvokeMethod
         3: dup
         4: invokespecial #3 // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: iconst_2
        10: iconst_3
        11: invokevirtual #4 // Method add:(II)I
        14: pop
        15: return
}

下面分几部分介绍调用相关的内容。

1.C++函数调用main()方法

现在我们从字节码索引为8的aload_1开始看,此时的栈帧状态如下:

由于aload\_1的tos\_out为atos,所以在栈顶缓存的寄存器中会缓存有TestInvokeMethod实例的地址,当执行iconst\_2时,会从atos进入。iconst\_2指令的汇编如下: 

// aep
push %rax
jmpq // 跳转到下面那条指令执行

// ...

mov $0x2,%eax // 指令的汇编代码

由于iconst\_2的tos\_out为itos,所以在进入下一个指令时,会从iconst\_3的tos\_int为itos中进入,如下:  

// iep
push %rax

mov $0x3,%eax

接下来就是执行invokevirtual字节码指令了,此时的2已经压入了表达式栈,而3在%eax寄存器中做为栈顶缓存,但是invokevirtual的tos_in为vtos,所以从invokevirtual字节码指令的iep进入时会将%eax寄存器中的值也压入表达式栈中,最终的栈状态如下图所示。

2.main()方法调用add()方法

invokevirtual字节码指令在执行时,假设此字节码指令已经解析完成,也就是对应的ConstantPoolCacheEntry中已经保存了方法调用相关的信息,则执行的相关汇编代码如下:

0x00007fffe1021f90: mov %r13,-0x38(%rbp) // 将bcp保存到栈中
// invokevirtual x中取出x,也就是常量池索引存储到%edx,
// 其实这里已经是ConstantPoolCacheEntry的index,因为在类的连接
// 阶段会对方法中特定的一些字节码指令进行重写
0x00007fffe1021f94: movzwl 0x1(%r13),%edx 
// 将ConstantPoolCache的首地址存储到%rcx
 
 
0x00007fffe1021f99: mov -0x28(%rbp),%rcx 
 
// 左移2位,因为%edx中存储的是ConstantPoolCacheEntry索引,左移2位是因为
// ConstantPoolCacheEntry占用4个字
0x00007fffe1021f9d: shl $0x2,%edx 
        
// 计算%rcx+%rdx*8+0x10,获取ConstantPoolCacheEntry[_indices,_f1,_f2,_flags]中的_indices
// 因为ConstantPoolCache的大小为0x16字节,%rcx+0x10定位
// 到第一个ConstantPoolCacheEntry的位置
// %rdx*8算出来的是相对于第一个ConstantPoolCacheEntry的字节偏移
0x00007fffe1021fa0: mov 0x10(%rcx,%rdx,8),%ebx 
 
// 获取ConstantPoolCacheEntry中indices[b2,b1,constant pool index]中的b2
0x00007fffe1021fa4: shr $0x18,%ebx 
 
// 取出indices中含有的b2,即bytecode存储到%ebx中
0x00007fffe1021fa7: and $0xff,%ebx 
 
// 查看182的bytecode是否已经连接 
0x00007fffe1021fad: cmp $0xb6,%ebx 
  
// 如果连接就进行跳转,跳转到resolved 
0x00007fffe1021fb3: je 0x00007fffe1022052

我们直接看方法解析后的逻辑实现,如下:

// **** resolved ****
// resolved的定义点,到这里说明invokevirtual字节码已经连接
// 获取ConstantPoolCacheEntry::_f2,这个字段只对virtual有意义
// 在计算时,因为ConstantPoolCacheEntry在ConstantPoolCache之后保存,
// 所以ConstantPoolCache为0x10,而
// _f2还要偏移0x10,这样总偏移就是0x20
// ConstantPoolCacheEntry::_f2存储到%rbx
0x00007fffe1022052: mov 0x20(%rcx,%rdx,8),%rbx 
 // ConstantPoolCacheEntry::_flags存储到%edx
0x00007fffe1022057: mov 0x28(%rcx,%rdx,8),%edx 
 // 将flags移动到ecx中
0x00007fffe102205b: mov %edx,%ecx 
// 从flags中取出参数大小 
0x00007fffe102205d: and $0xff,%ecx 
 
          
// 获取到recv,%rcx中保存的是参数大小,最终计算参数所需要的大小为%rsp+%rcx*8-0x8,
// flags中的参数大小对实例方法来说,已经包括了recv的大小
// 如调用实例方法的第一个参数是this(recv)
0x00007fffe1022063: mov -0x8(%rsp,%rcx,8),%rcx // recv保存到%rcx 
 
// 将flags存储到r13中
0x00007fffe1022068: mov %edx,%r13d 
// 从flags中获取return type,也就是从_flags的高4位保存的TosState
0x00007fffe102206b: shr $0x1c,%edx 
 
// 将TemplateInterpreter::invoke_return_entry地址存储到%r10
0x00007fffe102206e: movabs $0x7ffff73b6380,%r10 
// %rdx保存的是return type,计算返回地址
// 因为TemplateInterpreter::invoke_return_entry是数组,
// 所以要找到对应return type的入口地址
0x00007fffe1022078: mov (%r10,%rdx,8),%rdx 
// 向栈中压入返回地址
0x00007fffe102207c: push %rdx 
 
// 还原ConstantPoolCacheEntry::_flags 
0x00007fffe102207d: mov %r13d,%edx 
// 还原bcp
0x00007fffe1022080: mov -0x38(%rbp),%r13

执行完如上的代码后,已经向相关的寄存器中存储了相关的值。相关的寄存器状态如下:

rbx: 存储的是ConstantPoolCacheEntry::_f2属性的值
rcx: 就是调用实例方法时的第一个参数this
rdx: 存储的是ConstantPoolCacheEntry::_flags属性的值

栈的状态如下图所示。

需要注意的是return address也是一个例程的地址,是TemplateInterpreter::invoke\_return\_entry一维数组中类型为整数对应的下标存储的那个地址,因为调用add()方法返回的是整数类型。如何得出add()方法的返回类型呢?是从ConstantPoolCacheEntry的_flags的TosState中得出的。

下面继续看invokevirtual字节码指令将要执行的汇编代码,如下:

// flags存储到%eax
0x00007fffe1022084: mov %edx,%eax 
// 测试调用的方法是否为final 
0x00007fffe1022086: and $0x100000,%eax 
// 如果不为final就直接跳转到----notFinal---- 
0x00007fffe102208c: je 0x00007fffe10220c0 
 
// 通过(%rcx)来获取receiver的值,如果%rcx为空,则会引起OS异常
0x00007fffe1022092: cmp (%rcx),%rax 
 
// 省略统计相关代码部分
 
// 设置调用者栈顶并保存
0x00007fffe10220b4: lea 0x8(%rsp),%r13
0x00007fffe10220b9: mov %r13,-0x10(%rbp)
 
// 跳转到Method::_from_interpretered_entry入口去执行
0x00007fffe10220bd: jmpq *0x58(%rbx)

执行Method::\_from\_interpretered_entry例程,这个例程在之前详细介绍过,执行完成后会为add()方法创建栈帧,此时的栈状态如下图所示。

执行iload\_0与iload\_1指令,由于连续出现了2个iload,所以是\_fast\_iload2,汇编如下:

movzbl  0x1(%r13),%ebx
neg     %rbx
mov     (%r14,%rbx,8),%eax
push    %rax
movzbl  0x3(%r13),%ebx
neg     %rbx
mov     (%r14,%rbx,8),%eax

注意,只有第1个变量压入了栈,第2个则存储到%eax中做为栈顶缓存。 

调用iadd指令,由于tos_in为itos,所以汇编如下:

mov (%rsp),%edx
add    $0x8,%rsp
add    %edx,%eax

最后结果缓存在%eax中。 

3.退出add()方法

执行ireturn字节码指令进行add()方法的退栈操作。对于实例来说,执行的相关汇编代码如下:

// 将JavaThread::do_not_unlock_if_synchronized属性存储到%dl中
0x00007fffe101b770: mov 0x2ad(%r15),%dl
// 重置JavaThread::do_not_unlock_if_synchronized属性值为false
0x00007fffe101b777: movb $0x0,0x2ad(%r15)
 
// 将Method*加载到%rbx中
0x00007fffe101b77f: mov -0x18(%rbp),%rbx
// 将Method::_access_flags加载到%ecx中
0x00007fffe101b783: mov 0x28(%rbx),%ecx
// 检查Method::flags是否包含JVM_ACC_SYNCHRONIZED
0x00007fffe101b786: test $0x20,%ecx
// 如果方法不是同步方法,跳转到----unlocked----
0x00007fffe101b78c: je 0x00007fffe101b970

unlocked处的汇编实现如下:

// 将-0x8(%rbp)处保存的old stack pointer(saved rsp)取出来放到%rbx中
0x00007fffe101bac7: mov -0x8(%rbp),%rbx
 
// 移除栈帧
// leave指令相当于:
// mov %rbp, %rsp
// pop %rbp
0x00007fffe101bacb: leaveq 
// 将返回地址弹出到%r13中
0x00007fffe101bacc: pop %r13
// 设置%rsp为调用者的栈顶值
0x00007fffe101bace: mov %rbx,%rsp
0x00007fffe101bad1: jmpq *%r13

执行leaveq指令进行退栈操作,此时的栈状态如下图所示。

然后我们就要弹出返回地址,跳转到TemplateInterpreter::invoke\_return\_entry数组中保存的相关地址去执行对应的例程了。

4.执行返回例程

对于实例来说,传递的state为itos时生成的汇编代码如下:

// 将-0x10(%rbp)存储到%rsp后,置空-0x10(%rbp)
0x00007fffe1006ce0: mov -0x10(%rbp),%rsp // 更改rsp
0x00007fffe1006ce4: movq $0x0,-0x10(%rbp) // 更改栈中特定位置的值
// 恢复bcp和locals,使%r14指向本地变量表,%r13指向bcp
0x00007fffe1006cec: mov -0x38(%rbp),%r13
0x00007fffe1006cf0: mov -0x30(%rbp),%r14
 // 获取ConstantPoolCacheEntry的索引并加载到%ecx
0x00007fffe1006cf4: movzwl 0x1(%r13),%ecx 

 // 获取栈中-0x28(%rbp)的ConstantPoolCache并加载到%ecx
0x00007fffe1006cf9: mov -0x28(%rbp),%rbx 
// shl是逻辑左移,获取字偏移
0x00007fffe1006cfd: shl $0x2,%ecx 
// 获取ConstantPoolCacheEntry中的_flags属性值
0x00007fffe1006d00: mov 0x28(%rbx,%rcx,8),%ebx
// 获取_flags中的低8位中保存的参数大小
0x00007fffe1006d04: and $0xff,%ebx 

// lea指令将地址加载到内存寄存器中,也就是恢复调用方法之前栈的样子
0x00007fffe1006d0a: lea (%rsp,%rbx,8),%rsp 

// 跳转到下一指令执行
0x00007fffe1006d0e: movzbl 0x3(%r13),%ebx 
0x00007fffe1006d13: add $0x3,%r13
0x00007fffe1006d17: movabs $0x7ffff73b7ca0,%r10
0x00007fffe1006d21: jmpq *(%r10,%rbx,8)

如上的汇编代码也是执行的退栈操作,最主要的就是把在调用解释执行方法时压入的实参从栈中弹出,接着就是执行main()方法中invokevirtual中的下一条指令pop。此时的栈状态如下图所示。

需要注意的是,此时的栈顶缓存中存储着调用add()方法的执行结果,那么在跳转到下一条指令pop时,必须要从pop的iep入口进入,这样就能正确的执行下去了。 

5.退出main()方法 

当执行pop指令时,会从iep入口进入,执行的汇编代码如下:

// iep
push %rax

// ...

add    $0x8,%rsp

由于main()方法调用add()方法不需要返回结果,所以对于main()方法来说,这个结果会从main()方法的表达式栈中弹出。下面接着执行return指令,这个指令对应的汇编代码如下:

// 将JavaThread::do_not_unlock_if_synchronized属性存储到%dl中
0x00007fffe101b770: mov 0x2ad(%r15),%dl
// 重置JavaThread::do_not_unlock_if_synchronized属性值为false
0x00007fffe101b777: movb $0x0,0x2ad(%r15)

// 将Method*加载到%rbx中
0x00007fffe101b77f: mov -0x18(%rbp),%rbx
// 将Method::_access_flags加载到%ecx中
0x00007fffe101b783: mov 0x28(%rbx),%ecx
// 检查Method::flags是否包含JVM_ACC_SYNCHRONIZED
0x00007fffe101b786: test $0x20,%ecx
// 如果方法不是同步方法,跳转到----unlocked----
0x00007fffe101b78c: je 0x00007fffe101b970

main()方法为非同步方法,所以跳转到unlocked,在unlocked逻辑中会执行一些释放锁的逻辑,对于我们本实例来说这不重要,我们直接看退栈的操作,如下:

// 将-0x8(%rbp)处保存的old stack pointer(saved rsp)取出来放到%rbx中
0x00007fffe101bac7: mov -0x8(%rbp),%rbx

// 移除栈帧
// leave指令相当于:
// mov %rbp, %rsp
// pop %rbp
0x00007fffe101bacb: leaveq 
// 将返回地址弹出到%r13中
0x00007fffe101bacc: pop %r13
// 设置%rsp为调用者的栈顶值
0x00007fffe101bace: mov %rbx,%rsp
0x00007fffe101bad1: jmpq *%r13

最后的栈状态如下图所示。

其中的return address是C++语言的返回地址,接下来如何退出如上的一些栈帧及结束方法就是C++的事儿了。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK