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


    本文由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

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

    模板定义如下:

    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字节码指令对应的汇编代码如下: 

    第1部分

    // 将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处。

    第2部分

    如果执行如下汇编代码,则表示%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属性进行操作。

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

    第3部分

    在变量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处执行。

    第4部分

    在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()函数生成如下的汇编代码============
    

    第5部分

    // 如果是其它的return指令,则由于之前通过push指令将结果保存在
    // 表达式栈上,所以现在可通过pop将表达式栈上的结果弹出到对应寄存器中
    
    
    // **** unlocked ****
    // 在执行这里的代码时,表示当前的栈中没有相关的锁,也就是
    // 相关的锁对象已经全部释放
    
    // **** restart ****
    // 检查一下,是否所有的锁都已经释放了
    
    // %rsi指向当前栈中最靠栈顶的BasicObjectLock
    0x00007fffe101b970: mov -0x40(%rbp),%rsi
    // %rbx指向当前栈中最靠栈底的BasicObjectLock
    0x00007fffe101b974: lea -0x40(%rbp),%rbx
    
    // 跳转到----entry----
    0x00007fffe101b978: jmpq 0x00007fffe101ba8b
    

    第6部分

    执行如下代码,会通过调用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 ----
    

    第7部分

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

    第8部分

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

    第9部分

    // **** 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++的事儿了。

  • 相关阅读:
    快速进去CF(codeforces)的方法
    【Java】【25】去除空格
    【Java】【24】正则
    【Word&Excel】【4】Excel去除重复的项
    【Word&Excel】【3】Excel替换某一行/列的内容
    【JS】【22】标签的background-image属性
    【JS】【21】换行
    【Spring】【2】使用注解@Scheduled执行定时任务
    【JS】【20】点击页面判断是否安装app并打开,否则跳转下载的方法
    【Java】【23】汉字转拼音
  • 原文地址:https://www.cnblogs.com/perfma/p/15671803.html
Copyright © 2020-2023  润新知