您的位置:  首页 > 技术 > java语言 > 正文

【JVM源码解析】模板解释器解释执行Java字节码指令(下)

2021-11-26 12:00 https://my.oschina.net/PerfMa/blog/5325031 HeapDump社区 次阅读 条评论

【JVM源码解析】模板解释器解释执行Java字节码指令(上)

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

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

虚拟机规范中与运算相关的字节码指令如下表所示。

   
0x60iadd将栈顶两int型数值相加并将结果压入栈顶
0x61ladd将栈顶两long型数值相加并将结果压入栈顶
0x62fadd将栈顶两float型数值相加并将结果压入栈顶
0x63dadd将栈顶两double型数值相加并将结果压入栈顶
0x64isub将栈顶两int型数值相减并将结果压入栈顶
0x65lsub将栈顶两long型数值相减并将结果压入栈顶
0x66fsub将栈顶两float型数值相减并将结果压入栈顶
0x67dsub将栈顶两double型数值相减并将结果压入栈顶
0x68imul将栈顶两int型数值相乘并将结果压入栈顶
0x69lmul将栈顶两long型数值相乘并将结果压入栈顶
0x6afmul将栈顶两float型数值相乘并将结果压入栈顶
0x6bdmul将栈顶两double型数值相乘并将结果压入栈顶
0x6cidiv将栈顶两int型数值相除并将结果压入栈顶
0x6dldiv将栈顶两long型数值相除并将结果压入栈顶
0x6efdiv将栈顶两float型数值相除并将结果压入栈顶
0x6fddiv将栈顶两double型数值相除并将结果压入栈顶
0x70irem将栈顶两int型数值作取模运算并将结果压入栈顶
0x71lrem将栈顶两long型数值作取模运算并将结果压入栈顶
0x72frem将栈顶两float型数值作取模运算并将结果压入栈顶
0x73drem将栈顶两double型数值作取模运算并将结果压入栈顶
0x74ineg将栈顶int型数值取负并将结果压入栈顶
0x75lneg将栈顶long型数值取负并将结果压入栈顶
0x76fneg将栈顶float型数值取负并将结果压入栈顶
0x77dneg将栈顶double型数值取负并将结果压入栈顶
0x78ishl将int型数值左移位指定位数并将结果压入栈顶
0x79lshl将long型数值左移位指定位数并将结果压入栈顶
0x7aishr将int型数值右(符号)移位指定位数并将结果压入栈顶
0x7blshr将long型数值右(符号)移位指定位数并将结果压入栈顶
0x7ciushr将int型数值右(无符号)移位指定位数并将结果压入栈顶
0x7dlushr将long型数值右(无符号)移位指定位数并将结果压入栈顶
0x7eiand将栈顶两int型数值作“按位与”并将结果压入栈顶
0x7fland将栈顶两long型数值作“按位与”并将结果压入栈顶
0x80ior将栈顶两int型数值作“按位或”并将结果压入栈顶
0x81lor将栈顶两long型数值作“按位或”并将结果压入栈顶
0x82ixor将栈顶两int型数值作“按位异或”并将结果压入栈顶
0x83lxor将栈顶两long型数值作“按位异或”并将结果压入栈顶
0x84iinc将指定int型变量增加指定值(i++、i--、i+=2)
0x94lcmp比较栈顶两long型数值大小,并将结果(1、0或-1)压入栈顶
0x95fcmpl比较栈顶两float型数值大小,并将结果(1、0或-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
0x96fcmpg比较栈顶两float型数值大小,并将结果(1、0或-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
0x97dcmpl比较栈顶两double型数值大小,并将结果(1、0或-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
0x98dcmpg比较栈顶两double型数值大小,并将结果(1、0或-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶

 

1、基本加、减、乘与除指令

1、iadd指令

iadd指令将两个栈顶的整数相加,然后将相加的结果压入栈顶,其指令的格式如下:

iadd  val1,val2

val1与val2表示两个int类型的整数,在指令执行时,将val1与val3从操作数栈中出栈,将这两个数值相加得到 int 类型数据 result,将result压入操作数栈中。

iadd指令的模板定义如下:

def(Bytecodes::_iadd , ____|____|____|____, itos, itos, iop2 , add);

生成函数为TemplateTable::iop2(),实现如下:

void TemplateTable::iop2(Operation op) {  switch (op) {  case add  :                    __ pop_i(rdx); __ addl (rax, rdx); break;  case sub  : __ movl(rdx, rax); __ pop_i(rax); __ subl (rax, rdx); break;  case mul  :                    __ pop_i(rdx); __ imull(rax, rdx); break;  case _and :                    __ pop_i(rdx); __ andl (rax, rdx); break;  case _or  :                    __ pop_i(rdx); __ orl  (rax, rdx); break;  case _xor :                    __ pop_i(rdx); __ xorl (rax, rdx); break;  case shl  : __ movl(rcx, rax); __ pop_i(rax); __ shll (rax);      break;  case shr  : __ movl(rcx, rax); __ pop_i(rax); __ sarl (rax);      break;  case ushr : __ movl(rcx, rax); __ pop_i(rax); __ shrl (rax);      break;  default   : ShouldNotReachHere();

  }

}

可以看到,这个函数是许多指令的生成函数,如iadd、isub、imul、iand、ior、ixor、ishl、ishr、iushr。

为iadd指令生成的汇编代码如下: 

mov    (%rsp),%edxadd    $0x8,%rspadd    %edx,%eax

将栈顶与栈顶中缓存的%eax相加后的结果存储到%eax中。 

2、isub指令 

isub指令生成的汇编代码如下:

mov    %eax,%edxmov    (%rsp),%eaxadd    $0x8,%rspsub    %edx,%eax

代码实现比较简单,这里不再介绍。  

3、idiv指令

idiv是字节码除法指令,这个指令的格式如下:

idiv val1,val2

val1 和 val2 都必须为 int 类型数据,指令执行时,val1 和 val2从操作数栈中出栈,并且将这两个数值相除(val1÷val2),结果转换为 int 类型值 result,最后 result 被压入到操作数栈中。

idiv指令的模板定义如下:

def(Bytecodes::_idiv , ____|____|____|____, itos, itos, idiv ,  _  );

调用的生成函数为TemplateTable::idiv(),生成的汇编如下:

0x00007fffe1019707: mov    %eax,%ecx0x00007fffe1019709: mov    (%rsp),%eax0x00007fffe101970c: add    $0x8,%rsp



// 测试一下被除数是否为0x80000000,如果不是,就跳转到normal_case0x00007fffe1019710: cmp    $0x80000000,%eax0x00007fffe1019716: jne    0x00007fffe1019727// 被除数是0x80000000,而除数如果是-1的话,则跳转到special_case0x00007fffe101971c: xor    %edx,%edx0x00007fffe101971e: cmp    $0xffffffff,%ecx0x00007fffe1019721: je     0x00007fffe101972a// -- normal_case --



// cltd将eax寄存器中的数据符号扩展到edx:eax,具体就是

// 把eax的32位整数扩展为64位,高32位用eax的符号位填充保存到edx0x00007fffe1019727: cltd   

0x00007fffe1019728: idiv   %ecx



// -- special_case --

其中idiv函数会使用规定的寄存器,如下图所示。

汇编对0x80000000 / -1 这个特殊的除法做了检查。参考:利用反汇编调试与补码解释0x80000000 / -1整形输出异常不一致 

2、比较指令

lcmp指令比较栈顶两long型数值大小,并将结果(1、0或-1)压入栈顶。指令的格式如下:

lcmp val1,val2

val1 和 val2 都必须为 long 类型数据,指令执行时, val1 和 val2从操作数栈中出栈,使用一个 int 数值作为比较结果:

  • 如果 val1 大于val2,结果为 1;
  • 如果 val1 等于 val2,结果为 0;
  • 如果 val1小于 val2,结果为-1。

最后比较结果被压入到操作数栈中。

lcmp字节码指令的模板定义如下:

def(Bytecodes::_lcmp , ____|____|____|____, ltos, itos, lcmp ,  _ );

生成函数为TemplateTable::lcmp(), 生成的汇编如下:

0x00007fffe101a6c8: mov     (%rsp),%rdx0x00007fffe101a6cc: add     $0x10,%rsp// cmp指令描述如下:// 第1操作数<第2操作数时,ZF=0// 第1操作数=第2操作数时,ZF=1// 第1操作数>第2操作数时,ZF=00x00007fffe101a6d0: cmp     %rax,%rdx0x00007fffe101a6d3: mov     $0xffffffff,%eax // 将-1移到%eax中// 如果第1操作数小于第2操作数就跳转到done0x00007fffe101a6d8: jl      0x00007fffe101a6e0// cmp指令执行后,执行setne指令就能获取比较的结果// 根据eflags中的状态标志(CF,SF,OF,ZF和PF)将目标操作数设置为0或10x00007fffe101a6da: setne   %al0x00007fffe101a6dd: movzbl  %al,%eax//  -- done --

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

关于其它字节码指令的逻辑也相对简单,有兴趣的可自行研究。

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

Java虚拟机规范中定义的类型转换相关的字节码指令如下表所示。

   
0x85i2l将栈顶int型数值强制转换成long型数值并将结果压入栈顶
0x86i2f将栈顶int型数值强制转换成float型数值并将结果压入栈顶
0x87i2d将栈顶int型数值强制转换成double型数值并将结果压入栈顶
0x88l2i将栈顶long型数值强制转换成int型数值并将结果压入栈顶
0x89l2f将栈顶long型数值强制转换成float型数值并将结果压入栈顶
0x8al2d将栈顶long型数值强制转换成double型数值并将结果压入栈顶
0x8bf2i将栈顶float型数值强制转换成int型数值并将结果压入栈顶
0x8cf2l将栈顶float型数值强制转换成long型数值并将结果压入栈顶
0x8df2d将栈顶float型数值强制转换成double型数值并将结果压入栈顶
0x8ed2i将栈顶double型数值强制转换成int型数值并将结果压入栈顶
0x8fd2l将栈顶double型数值强制转换成long型数值并将结果压入栈顶
0x90d2f将栈顶double型数值强制转换成float型数值并将结果压入栈顶
0x91i2b将栈顶int型数值强制转换成byte型数值并将结果压入栈顶
0x92i2c将栈顶int型数值强制转换成char型数值并将结果压入栈顶
0x93i2s将栈顶int型数值强制转换成short型数值并将结果压入栈顶

上表字节码指令的模板定义如下:

def(Bytecodes::_i2l   , ____|____|____|____, itos, ltos, convert ,  _           );

def(Bytecodes::_i2f   , ____|____|____|____, itos, ftos, convert ,  _           );

def(Bytecodes::_i2d   , ____|____|____|____, itos, dtos, convert ,  _           );

def(Bytecodes::_l2i   , ____|____|____|____, ltos, itos, convert ,  _           );

def(Bytecodes::_l2f   , ____|____|____|____, ltos, ftos, convert ,  _           );

def(Bytecodes::_l2d   , ____|____|____|____, ltos, dtos, convert ,  _           );

def(Bytecodes::_f2i   , ____|____|____|____, ftos, itos, convert ,  _           );

def(Bytecodes::_f2l   , ____|____|____|____, ftos, ltos, convert ,  _           );

def(Bytecodes::_f2d   , ____|____|____|____, ftos, dtos, convert ,  _           );

def(Bytecodes::_d2i   , ____|____|____|____, dtos, itos, convert ,  _           );

def(Bytecodes::_d2l   , ____|____|____|____, dtos, ltos, convert ,  _           );

def(Bytecodes::_d2f   , ____|____|____|____, dtos, ftos, convert ,  _           );

def(Bytecodes::_i2b   , ____|____|____|____, itos, itos, convert ,  _           );

def(Bytecodes::_i2c   , ____|____|____|____, itos, itos, convert ,  _           );

def(Bytecodes::_i2s   , ____|____|____|____, itos, itos, convert ,  _           );

相关字节码转换指令的生成函数为TemplateTable::convert(),此函数的实现如下:

void TemplateTable::convert() {  static const int64_t is_nan = 0x8000000000000000L;  // Conversion

  switch (bytecode()) {  case Bytecodes::_i2l:

    __ movslq(rax, rax);    break;  case Bytecodes::_i2f:

    __ cvtsi2ssl(xmm0, rax);    break;  case Bytecodes::_i2d:

    __ cvtsi2sdl(xmm0, rax);    break;  case Bytecodes::_i2b:

    __ movsbl(rax, rax);    break;  case Bytecodes::_i2c:

    __ movzwl(rax, rax);    break;  case Bytecodes::_i2s:

    __ movswl(rax, rax);    break;  case Bytecodes::_l2i:

    __ movl(rax, rax);    break;  case Bytecodes::_l2f:

    __ cvtsi2ssq(xmm0, rax);    break;  case Bytecodes::_l2d:

    __ cvtsi2sdq(xmm0, rax);    break;  case Bytecodes::_f2i:

  {

    Label L;

    __ cvttss2sil(rax, xmm0);

    __ cmpl(rax, 0x80000000); // NaN or overflow/underflow?

    __ jcc(Assembler::notEqual, L);

    __ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::f2i), 1);

    __ bind(L);

  }    break;  case Bytecodes::_f2l:

  {

    Label L;

    __ cvttss2siq(rax, xmm0);    // NaN or overflow/underflow?

    __ cmp64(rax, ExternalAddress((address) &is_nan));

    __ jcc(Assembler::notEqual, L);

    __ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::f2l), 1);

    __ bind(L);

  }    break;  case Bytecodes::_f2d:

    __ cvtss2sd(xmm0, xmm0);    break;  case Bytecodes::_d2i:

  {

    Label L;

    __ cvttsd2sil(rax, xmm0);

    __ cmpl(rax, 0x80000000); // NaN or overflow/underflow?

    __ jcc(Assembler::notEqual, L);

    __ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::d2i), 1);

    __ bind(L);

  }    break;  case Bytecodes::_d2l:

  {

    Label L;

    __ cvttsd2siq(rax, xmm0);    // NaN or overflow/underflow?

    __ cmp64(rax, ExternalAddress((address) &is_nan));

    __ jcc(Assembler::notEqual, L);

    __ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::d2l), 1);

    __ bind(L);

  }    break;  case Bytecodes::_d2f:

    __ cvtsd2ss(xmm0, xmm0);    break;  default:    ShouldNotReachHere();

  }

}

如_i2l指令将栈顶int型数值强制转换成long型数值并将结果压入栈顶,其对应的汇编代码如下:

movslq %eax,%rax  // 将一个双字扩展后送到一个四字中

对于浮点数float或long转int或long类型相对复杂,下面看一个float转int类型的f2i指令。

// 把标量单精度数转换为占用双字的标量整数0x00007fffe1019189: vcvttss2si %xmm0,%eax

// 和0x80000000进行比较,如果不相等,则跳转到L0x00007fffe101918d: cmp    $0x80000000,%eax0x00007fffe1019193: jne    0x00007fffe10191bc// 如果栈顶指针已经按16字节对齐,则可直接调用调用SharedRuntime::f2i()函数,否则

// 将栈顶指令按16字节对齐后再调用0x00007fffe1019199: test   $0xf,%esp0x00007fffe101919f: je     0x00007fffe10191b70x00007fffe10191a5: sub    $0x8,%rsp

// 调用SharedRuntime::f2i()函数0x00007fffe10191a9: callq  0x00007ffff6a0f9460x00007fffe10191ae: add    $0x8,%rsp0x00007fffe10191b2: jmpq   0x00007fffe10191bc// 调用SharedRuntime::f2i()函数0x00007fffe10191b7: callq  0x00007ffff6a0f946 ---- L ----

生成的汇编指令vcvttss2si的意思为把标量单精度数转换为占用双字的标量整数,名称的由来解读如下:

cvt:convert,转换;

t:truncation,截断;

ss:scalar single,标量单精度数;

2:to;

si:scalar integer,标量整数。

调用的SharedRuntime::f2i()函数的实现如下:

JRT_LEAF(jint, SharedRuntime::f2i(jfloat  x))  if (g_isnan(x))  // 如果为非数字值,直接返回0

    return 0;  if (x >= (jfloat) max_jint)    return max_jint;  if (x <= (jfloat) min_jint)    return min_jint;  return (jint) x;

JRT_END

C++函数调用时,需要一个参数x,在GNU / Linux上遵循System V AMD64 ABI的调用约定。寄存器RDI,RSI,RDX,RCX,R8和R9是用于整数和存储器地址的参数和XMM0,XMM1,XMM2,XMM3,XMM4,XMM5,XMM6和XMM7用于浮点参数,所以将用xmm0做为第1个参数,这个参数恰好是栈顶用来缓存浮点数的寄存器,所以默认不用任何操作。 

返回值存储到%rax中,由于tos_out为itos,%rax寄存器用来做栈顶缓存,所以也不需要做额外的操作。

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

Java虚拟机规范中定义的对象操作相关的字节码指令如下表所示。 

   
0xb2getstatic获取指定类的静态域,并将其值压入栈顶
0xb3putstatic为指定的类的静态域赋值
0xb4getfield获取指定类的实例域,并将其值压入栈顶
0xb5putfield为指定的类的实例域赋值
0xbbnew创建一个对象,并将其引用值压入栈顶
0xbcnewarray创建一个指定原始类型(如int,、float,、char等)的数组,并将其引用值压入栈顶
0xbdanewarray创建一个引用型(如类、接口或数组)的数组,并将其引用值压入栈顶
0xbearraylength获得数组的长度值并压入栈顶
0xc0checkcast检验类型转换,检验未通过将抛出ClassCastException
0xc1instanceof检验对象是否是指定的类的实例,如果是将1压入栈顶,否则将0压入栈顶
0xc5multianewarray创建指定类型和指定维度的多维数组(执行该指令时,操作栈中必须包含各维度的长度值),并将其引用值压入栈顶

字节码指令的模板定义如下:

def(Bytecodes::_getstatic           , ubcp|____|clvm|____, vtos, vtos, getstatic           , f1_byte      );

def(Bytecodes::_putstatic           , ubcp|____|clvm|____, vtos, vtos, putstatic           , f2_byte      );

def(Bytecodes::_getfield            , ubcp|____|clvm|____, vtos, vtos, getfield            , f1_byte      );

def(Bytecodes::_putfield            , ubcp|____|clvm|____, vtos, vtos, putfield            , f2_byte      );



def(Bytecodes::_new                 , ubcp|____|clvm|____, vtos, atos, _new                ,  _           );

def(Bytecodes::_newarray            , ubcp|____|clvm|____, itos, atos, newarray            ,  _           );

def(Bytecodes::_anewarray           , ubcp|____|clvm|____, itos, atos, anewarray           ,  _           );

def(Bytecodes::_multianewarray      , ubcp|____|clvm|____, vtos, atos, multianewarray      ,  _           );



def(Bytecodes::_arraylength         , ____|____|____|____, atos, itos, arraylength         ,  _           );



def(Bytecodes::_checkcast           , ubcp|____|clvm|____, atos, atos, checkcast           ,  _           );



def(Bytecodes::_instanceof          , ubcp|____|clvm|____, atos, itos, instanceof          ,  _           );

new字节码指令的生成函数为TemplateTable::_new(),这在《深入剖析Java虚拟机:源码剖析与实例详解(基础卷)》的第9章类对象创建时详细介绍过,这里不再介绍。

getstatic字节码指令获取指定类的静态域,并将其值压入栈顶。格式如下:

getstatic indexbyte1 indexbyte2

无符号数indexbyte1和indexbyte2构建为(indexbyte1<<8)|indexbyte2,这个值指明了一个当前类的运行时常量池索引值,指向的运行时常量池项为一个字段的符号引用。

getstatic字节码指令的生成函数为TemplateTable::getstatic(),还有个类似的getfield指令,这些生成函数如下:

void TemplateTable::getfield(int byte_no) {  getfield_or_static(byte_no, false); // getfield的byte_no值为1}void TemplateTable::getstatic(int byte_no) {  getfield_or_static(byte_no, true); // getstatic的byte_no的值为1}

最终都会调用getfield_or_static()函数生成机器指令片段。此函数生成的机器指令片段对应的汇编代码如下:

// 获取ConstantPoolCache中ConstantPoolCacheEntry的index0x00007fffe101fd10: movzwl 0x1(%r13),%edx// 从栈中获取ConstantPoolCache的首地址0x00007fffe101fd15: mov    -0x28(%rbp),%rcx// 左移2位,因为%edx中存储的是ConstantPoolCacheEntry index,// 左移2位是因为ConstantPoolCacheEntry的内存占用是4个字0x00007fffe101fd19: shl    $0x2,%edx// 计算%rcx+%rdx*8+0x10,获取ConstantPoolCacheEntry[_indices,_f1,_f2,_flags]中的_indices// 因为ConstantPoolCache的大小为0x16字节,%rcx+0x10定位到第一个ConstantPoolCacheEntry的开始位置// %rdx*8算出来的是相对于第一个ConstantPoolCacheEntry的字节偏移0x00007fffe101fd1c: mov    0x10(%rcx,%rdx,8),%ebx// _indices向右移动16位后获取[get bytecode,set bytecode,original constant pool index]中的get bytecode与set bytecode0x00007fffe101fd20: shr    $0x10,%ebx// 获取set bytecode字段的值0x00007fffe101fd23: and    $0xff,%ebx// 0xb2是getstatic指令的Opcode,比较值,如果相等就说明已经连接,跳转到resolved0x00007fffe101fd29: cmp    $0xb2,%ebx0x00007fffe101fd2f: je     0x00007fffe101fdce// 将getstatic字节码的Opcode存储到%ebx中0x00007fffe101fd35: mov    $0xb2,%ebx// 省略通过调用MacroAssembler::call_VM()函数来执行InterpreterRuntime::resolve_get_put()函数的汇编代码// ...

调用MacroAssembler::call_VM()函数生成如下代码,通过这些代码来执行InterpreterRuntime::resolve_get_put()函数。MacroAssembler::call_VM()函数的汇编在之前已经详细介绍过,这里不再介绍,直接给出汇编代码,如下:

0x00007fffe101fd3a: callq  0x00007fffe101fd440x00007fffe101fd3f: jmpq   0x00007fffe101fdc20x00007fffe101fd44: mov    %rbx,%rsi0x00007fffe101fd47: lea    0x8(%rsp),%rax0x00007fffe101fd4c: mov    %r13,-0x38(%rbp)0x00007fffe101fd50: mov    %r15,%rdi0x00007fffe101fd53: mov    %rbp,0x200(%r15)0x00007fffe101fd5a: mov    %rax,0x1f0(%r15)0x00007fffe101fd61: test   $0xf,%esp0x00007fffe101fd67: je     0x00007fffe101fd7f0x00007fffe101fd6d: sub    $0x8,%rsp0x00007fffe101fd71: callq  0x00007ffff66b567c0x00007fffe101fd76: add    $0x8,%rsp0x00007fffe101fd7a: jmpq   0x00007fffe101fd840x00007fffe101fd7f: callq  0x00007ffff66b567c0x00007fffe101fd84: movabs $0x0,%r100x00007fffe101fd8e: mov    %r10,0x1f0(%r15)0x00007fffe101fd95: movabs $0x0,%r100x00007fffe101fd9f: mov    %r10,0x200(%r15)0x00007fffe101fda6: cmpq   $0x0,0x8(%r15)0x00007fffe101fdae: je     0x00007fffe101fdb90x00007fffe101fdb4: jmpq   0x00007fffe10004200x00007fffe101fdb9: mov    -0x38(%rbp),%r130x00007fffe101fdbd: mov    -0x30(%rbp),%r140x00007fffe101fdc1: retq

如上代码完成的事情很简单,就是调用C++函数编写的InterpreterRuntime::resolve_get_put()函数,此函数会填充常量池缓存中ConstantPoolCacheEntry信息,关于ConstantPoolCache以及ConstantPoolCacheEntry,还有ConstantPoolCacheEntry中各个字段的含义在《深入剖析Java虚拟机:源码剖析与实例详解(基础卷)》中已经详细介绍过,这里不再介绍。 

InterpreterRuntime::resolve_get_put()函数的实现比较多,我们首先看一部分实现,如下:

IRT_ENTRY(void, InterpreterRuntime::resolve_get_put(JavaThread* thread, Bytecodes::Code bytecode))  // resolve field

  fieldDescriptor      info;

  constantPoolHandle   pool(thread, method(thread)->constants());  bool  is_put    = (bytecode == Bytecodes::_putfield  || bytecode == Bytecodes::_putstatic);  bool  is_static = (bytecode == Bytecodes::_getstatic || bytecode == Bytecodes::_putstatic);



  {

    JvmtiHideSingleStepping jhss(thread);    int x = get_index_u2_cpcache(thread, bytecode); // 根据线程栈中的bcp来获取常量池缓存索引

    LinkResolver::resolve_field_access(info, pool, x ,bytecode, CHECK); // 向info中收集信息

  } 



  // check if link resolution caused cpCache to be updated

  if (already_resolved(thread)){

      return;

  }



   ...}

调用get_index_u2_cpcache()函数从当前方法对应的栈帧中获取bcp,然后通过bcp来获取字节码指令的操作数,也就是常量池索引,得到常量池索引后调用LinkResolver::resolve_field_access()函数可能会连接类和字段,然后将查询到的字段相关信息存储到fieldDescriptor中。resolve_field_access()函数的实现如下:

void LinkResolver::resolve_field_access(

 fieldDescriptor&     result,

 constantPoolHandle   pool, int                  index, // 常量池索引

 Bytecodes::Code      byte,

 TRAPS

) { 

  Symbol* field = pool->name_ref_at(index);

  Symbol* sig   = pool->signature_ref_at(index);  // resolve specified klass  连接特定的类

  KlassHandle resolved_klass;

  resolve_klass(resolved_klass, pool, index, CHECK);



  KlassHandle  current_klass(THREAD, pool->pool_holder());

  resolve_field(result, resolved_klass, field, sig, current_klass, byte, true, true, CHECK);

} 

从pool中查找到的index处的索引项为CONSTANT_NameAndType_info,格式如下:

CONSTANT_NameAndType_info {

   u1 tag;

   u2 name_index;       // 占用16位

   u2 descriptor_index; // 占用16位

}

常量池中的一个CONSTANT_NameAndType_info数据项, 可以看做CONSTANT_NameAndType类型的一个实例 。 从这个数据项的名称可以看出, 它描述了两种信息,第一种信息是名称(Name), 第二种信息是类型(Type) 。这里的名称是指方法的名称或者字段的名称, 而Type是广义上的类型,它其实描述的是字段的描述符或方法的描述符。 也就是说, 如果Name部分是一个字段名称,那么Type部分就是相应字段的描述符; 如果Name部分描述的是一个方法的名称,那么Type部分就是对应的方法的描述符。 也就是说,一个CONSTANT_NameAndType_info就表示了一个方法或一个字段。

调用resolve_klass()连接类,调用resolve_field()连接字段。在resolve_field()函数中有如下实现:

InstanceKlass* tmp = InstanceKlass::cast(resolved_klass());

KlassHandle    sel_klass(THREAD, tmp->find_field(field, sig, &fd));

最重要的就是调用InstanceKlass的find_field()函数查找字段,将查找到的相关信息存储到fieldDescriptor类型的fd中。关于字段在InstanceKlass中的存储以及具体的布局在《深入剖析Java虚拟机:源码剖析与实例详解(基础卷)》中已经详细介绍过,这里不再介绍。 

fieldDescriptor类及重要属性的定义如下:

class fieldDescriptor VALUE_OBJ_CLASS_SPEC { private:

  AccessFlags          _access_flags;  int                  _index; // the field index

  constantPoolHandle   _cp;

  ...

}

其中的_access_flags可用来表示字段是否有volatile、final等关键字修饰,_index表示字段是存储在InstanceKlass中相应数组的第几个元组中。_cp表示定义当前字段的类的常量池。

通过调用resolve_klass()和resolve_field()函数后就可拿到这些信息,然后返回到InterpreterRuntime::resolve_get_put()函数继续查看实现逻辑:

TosState state  = as_TosState(info.field_type());



  Bytecodes::Code put_code = (Bytecodes::Code)0;





  InstanceKlass* klass = InstanceKlass::cast(info.field_holder());  bool uninitialized_static = (  (bytecode == Bytecodes::_getstatic || bytecode == Bytecodes::_putstatic) &&

                                 !klass->is_initialized()    );

  Bytecodes::Code get_code = (Bytecodes::Code)0;  if (!uninitialized_static) {

    get_code = ((is_static) ? Bytecodes::_getstatic : Bytecodes::_getfield);    // 1、是putfield或putstatic指令

    // 2、是getstatic或getfield指令并且不是获取final变量的值

    if (is_put || !info.access_flags().is_final()) {

      put_code = ((is_static) ? Bytecodes::_putstatic : Bytecodes::_putfield);

    }

  }



  ConstantPoolCacheEntry* cpce = cache_entry(thread);

  cpce->set_field(

    get_code,            // 设置的是_indices中的b1,当为getstatic或getfield时,则其中存储的是Opcode

    put_code,            // 设置的是_indices中的b2,当为setstatic或setfield时,则其中存储的是Opcode,所以get_code与put_code如果要连接了,其值不为0

    info.field_holder(), // 设置的是_f1字段,表示字段的拥有者

    info.index(),                      // field_index,设置的是flags

    info.offset(),                     // field_offset,设置的是_f2字段,Offset (in words) of field from start of instanceOop / Klass*

    state,                             // field_type,设置的是flags

    info.access_flags().is_final(),    // 设置的是flags

    info.access_flags().is_volatile(), // 设置的是flags

    pool->pool_holder()

  );

通过info中的信息就可以得到字段的各种信息,然后填充ConstantPoolEntry信息,这样下次就不用对字段进行连接了,或者说不用从InstanceKlass中查找字段信息了,可直接从ConstantPoolCacheEntry中找到所有想得到的信息。 

  

上图在《深入剖析Java虚拟机:源码剖析与实例详解(基础卷)》一书中详细介绍过,通过我们解读getstatic字节码的解释执行过程,可以清楚的知道常量池缓存项的作用。对于getstatic来说,开始就会判断_indices中的高8位存储的是否为getstatic的操作码,如果不是,则表示没有连接,所以要调用InterpreterRuntime::resolve_get_put()函数进行连接操作。 

在连接完成或已经连接完成时会继续执行如下汇编代码:

// 将ConstantPoolCacheEntry的索引存储么%edx0x00007fffe101fdc2: movzwl 0x1(%r13),%edx// 将ConstantPoolCache的首地址存储到%rcx0x00007fffe101fdc7: mov    -0x28(%rbp),%rcx// 获取对应的ConstantPoolCacheEntry对应的索引0x00007fffe101fdcb: shl    $0x2,%edx// --resolved --// 获取[_indices,_f1,_f2,_flags]中的_f2,由于ConstantPoolCache占用16字节,而_indices// 和_f2各占用8字节,所以_f2的偏移为32字节,也就是0x32// _f2中保存的是字段在java.lang.Class实例中的字节偏移,通过此偏移就可获取此字段存储在// java.lang.Class实例的值0x00007fffe101fdce: mov    0x20(%rcx,%rdx,8),%rbx// 获取[_indices,_f1,_f2,_flags]中的_flags 0x00007fffe101fdd3: mov 0x28(%rcx,%rdx,8),%eax// 获取[_indices,_f1,_f2,_flags]中的_f1,_f1保存了字段拥有者,// 也就是java.lang.Class对象0x00007fffe101fdd7: mov 0x18(%rcx,%rdx,8),%rcx// 从_f1中获取_java_mirror属性的值0x00007fffe101fddc: mov    0x70(%rcx),%rcx// 将_flags向右移动28位,剩下TosState0x00007fffe101fde0: shr    $0x1c,%eax0x00007fffe101fde3: and    $0xf,%eax// 如果不相等,说明TosState的值不为0,则跳转到notByte0x00007fffe101fde6: jne    0x00007fffe101fdf6// btos// btos的编号为0,代码执行到这里时,可能栈顶缓存要求是btos// %rcx中存储的是_java_mirror,%rbx中存储的是_f2,由于静态变量存储在_java_mirror中,所以要获取// 对应的首地址并压入栈中0x00007fffe101fdec: movsbl (%rcx,%rbx,1),%eax0x00007fffe101fdf0: push   %rax// 跳转到Done0x00007fffe101fdf1: jmpq   0x00007fffe101ff0c// -- notByte --// %eax中存储的是TosState,如果不为atos,则跳转到notObj0x00007fffe101fdf6: cmp    $0x7,%eax0x00007fffe101fdf9: jne    0x00007fffe101fe90// atos// %rcx中存储的是_java_mirror,%rbx中存储的是_f2,// 所以要获取静态变量的首地址并压入栈内0x00007fffe101fdff: mov    (%rcx,%rbx,1),%eax0x00007fffe101fe02: push   %r100x00007fffe101fe04: cmp    0x163a8d45(%rip),%r12   # 0x00007ffff73c8b50 0x00007fffe101fe0b: je     0x00007fffe101fe880x00007fffe101fe11: mov    %rsp,-0x28(%rsp)0x00007fffe101fe16: sub    $0x80,%rsp0x00007fffe101fe1d: mov    %rax,0x78(%rsp)0x00007fffe101fe22: mov    %rcx,0x70(%rsp)0x00007fffe101fe27: mov    %rdx,0x68(%rsp)0x00007fffe101fe2c: mov    %rbx,0x60(%rsp)0x00007fffe101fe31: mov    %rbp,0x50(%rsp)0x00007fffe101fe36: mov    %rsi,0x48(%rsp)0x00007fffe101fe3b: mov    %rdi,0x40(%rsp)0x00007fffe101fe40: mov    %r8,0x38(%rsp)0x00007fffe101fe45: mov    %r9,0x30(%rsp)0x00007fffe101fe4a: mov    %r10,0x28(%rsp)0x00007fffe101fe4f: mov    %r11,0x20(%rsp)0x00007fffe101fe54: mov    %r12,0x18(%rsp)0x00007fffe101fe59: mov    %r13,0x10(%rsp)0x00007fffe101fe5e: mov    %r14,0x8(%rsp)0x00007fffe101fe63: mov    %r15,(%rsp)0x00007fffe101fe67: movabs $0x7ffff6d4d828,%rdi0x00007fffe101fe71: movabs $0x7fffe101fe11,%rsi0x00007fffe101fe7b: mov    %rsp,%rdx0x00007fffe101fe7e: and    $0xfffffffffffffff0,%rsp0x00007fffe101fe82: callq  0x00007ffff6872e3a0x00007fffe101fe87: hlt 

0x00007fffe101fe88: pop    %r100x00007fffe101fe8a: push   %rax0x00007fffe101fe8b: jmpq   0x00007fffe101ff0c// -- notObj --0x00007fffe101fe90: cmp    $0x3,%eax// 如果不为itos,则跳转到notInt0x00007fffe101fe93: jne    0x00007fffe101fea2// itos0x00007fffe101fe99: mov    (%rcx,%rbx,1),%eax0x00007fffe101fe9c: push   %rax// 跳转到Done0x00007fffe101fe9d: jmpq   0x00007fffe101ff0c// -- notInt --// 如果不为ctos,则跳转到notChar0x00007fffe101fea2: cmp    $0x1,%eax0x00007fffe101fea5: jne    0x00007fffe101feb5// ctos0x00007fffe101feab: movzwl (%rcx,%rbx,1),%eax0x00007fffe101feaf: push   %rax// 跳转到Done0x00007fffe101feb0: jmpq   0x00007fffe101ff0c// -- notChar --// 如果不为stos,则跳转到notShort0x00007fffe101feb5: cmp    $0x2,%eax0x00007fffe101feb8: jne    0x00007fffe101fec8// stos0x00007fffe101febe: movswl (%rcx,%rbx,1),%eax0x00007fffe101fec2: push   %rax// 跳转到done0x00007fffe101fec3: jmpq   0x00007fffe101ff0c// -- notShort --// 如果不为ltos,则跳转到notLong0x00007fffe101fec8: cmp    $0x4,%eax0x00007fffe101fecb: jne    0x00007fffe101fee2// ltos0x00007fffe101fed1: mov    (%rcx,%rbx,1),%rax0x00007fffe101fed5: sub    $0x10,%rsp0x00007fffe101fed9: mov    %rax,(%rsp)// 跳转到Done0x00007fffe101fedd: jmpq   0x00007fffe101ff0c// -- notLong --// 如果不为ftos,则跳转到notFloat0x00007fffe101fee2: cmp    $0x5,%eax0x00007fffe101fee5: jne    0x00007fffe101fefe// ftos0x00007fffe101feeb: vmovss (%rcx,%rbx,1),%xmm00x00007fffe101fef0: sub    $0x8,%rsp0x00007fffe101fef4: vmovss %xmm0,(%rsp)// 跳转到Done0x00007fffe101fef9: jmpq   0x00007fffe101ff0c// -- notFloat --0x00007fffe101fefe: vmovsd (%rcx,%rbx,1),%xmm00x00007fffe101ff03: sub    $0x10,%rsp0x00007fffe101ff07: vmovsd %xmm0,(%rsp)  

  // -- Done --  

如上汇编代码虽然多,但是完成的逻辑却非常简单,就是通过ConstantPoolCacheEntry中存储的信息(所谓的字节码连接完成指的就是对应的常量池缓存项的信息已经完善)完成压栈的逻辑。由于静态字段的值存储在java.lang.Class实例中,所以需要获取到对应的值,然后根据栈顶缓存要求的状态将值压入表达式栈即可。

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

getfield指令表示获取指定类的实例域,并将其值压入栈顶。其格式如下:

getstatic indexbyte1 indexbyte2

无符号数indexbyte1和indexbyte2构建为(indexbyte1<<8)|indexbyte2,这个值指明了一个当前类的运行时常量池索引值,指向的运行时常量池项为一个字段的符号引用。

getfield字节码指令的生成函数为TemplateTable::getfield(),这些生成函数如下:

void TemplateTable::getfield(int byte_no) {  getfield_or_static(byte_no, false); // getfield的byte_no值为1}

最终会调用getfield_or_static()函数生成机器指令片段。此函数生成的机器指令片段对应的汇编代码如下:

0x00007fffe10202d0: movzwl 0x1(%r13),%edx0x00007fffe10202d5: mov    -0x28(%rbp),%rcx0x00007fffe10202d9: shl    $0x2,%edx0x00007fffe10202dc: mov    0x10(%rcx,%rdx,8),%ebx0x00007fffe10202e0: shr    $0x10,%ebx0x00007fffe10202e3: and    $0xff,%ebx// 0xb4是getfield指令的Opcode,如果相等,则说明已经连接,直接跳转到resolved0x00007fffe10202e9: cmp    $0xb4,%ebx0x00007fffe10202ef: je     0x00007fffe102038e0x00007fffe10202f5: mov    $0xb4,%ebx// 省略通过调用MacroAssembler::call_VM()函数来执行 // InterpreterRuntime::resolve_get_put()函数的汇编代码 // ...

调用MacroAssembler::call_VM()函数生成如下代码,通过这些代码来执行InterpreterRuntime::resolve_get_put()函数。MacroAssembler::call_VM()函数的汇编在之前已经详细介绍过,这里不再介绍,直接给出汇编代码,如下:

0x00007fffe10202fa: callq  0x00007fffe10203040x00007fffe10202ff: jmpq   0x00007fffe10203820x00007fffe1020304: mov    %rbx,%rsi0x00007fffe1020307: lea    0x8(%rsp),%rax0x00007fffe102030c: mov    %r13,-0x38(%rbp)0x00007fffe1020310: mov    %r15,%rdi0x00007fffe1020313: mov    %rbp,0x200(%r15)0x00007fffe102031a: mov    %rax,0x1f0(%r15)0x00007fffe1020321: test   $0xf,%esp0x00007fffe1020327: je     0x00007fffe102033f0x00007fffe102032d: sub    $0x8,%rsp0x00007fffe1020331: callq  0x00007ffff66b567c0x00007fffe1020336: add    $0x8,%rsp0x00007fffe102033a: jmpq   0x00007fffe10203440x00007fffe102033f: callq  0x00007ffff66b567c0x00007fffe1020344: movabs $0x0,%r100x00007fffe102034e: mov    %r10,0x1f0(%r15)0x00007fffe1020355: movabs $0x0,%r100x00007fffe102035f: mov    %r10,0x200(%r15)0x00007fffe1020366: cmpq   $0x0,0x8(%r15)0x00007fffe102036e: je     0x00007fffe10203790x00007fffe1020374: jmpq   0x00007fffe10004200x00007fffe1020379: mov    -0x38(%rbp),%r130x00007fffe102037d: mov    -0x30(%rbp),%r140x00007fffe1020381: retq

如上代码完成的事情很简单,就是调用C++函数编写的InterpreterRuntime::resolve_get_put()函数,此函数会填充常量池缓存中ConstantPoolCacheEntry信息,关于ConstantPoolCache以及ConstantPoolCacheEntry,还有ConstantPoolCacheEntry中各个字段的含义在《深入剖析Java虚拟机:源码剖析与实例详解(基础卷)》中已经详细介绍过,这里不再介绍。

0x00007fffe1020382: movzwl 0x1(%r13),%edx0x00007fffe1020387: mov    -0x28(%rbp),%rcx0x00007fffe102038b: shl    $0x2,%edx



---- resolved ---- 



// 获取[_indices,_f1,_f2,_flags]中的_f2,由于ConstantPoolCache占用16字节,而_indices// 和_f2各占用8字节,所以_f2的偏移为32字节,也就是0x32// _f2中保存的是字段在oop实例中的字节偏移,通过此偏移就可获取此字段存储在// oop中的值0x00007fffe102038e: mov    0x20(%rcx,%rdx,8),%rbx// 获取[_indices,_f1,_f2,_flags]中的_flags 0x00007fffe1020393: mov    0x28(%rcx,%rdx,8),%eax// 将栈中的objectref对象弹出到%rcx中0x00007fffe1020397: pop    %rcx// provoke(激起; 引起; 引发) OS NULL exception if reg = NULL by// accessing M[reg] w/o changing any (non-CC) registers// NOTE: cmpl is plenty(足够) here to provoke a segv0x00007fffe1020398: cmp    (%rcx),%rax// 将_flags向右移动28位,剩下TosState0x00007fffe102039b: shr    $0x1c,%eax0x00007fffe102039e: and    $0xf,%eax// 如果不相等,说明TosState的值不为0,则跳转到notByte0x00007fffe10203a1: jne    0x00007fffe10203ba// btos// btos的编号为0,代码执行到这里时,可能栈顶缓存要求是btos// %rcx中存储的是objectref,%rbx中存储的是_f2,获取字段对应的值存储到%rax中0x00007fffe10203a7: movsbl (%rcx,%rbx,1),%eax0x00007fffe10203ab: push   %rax// 对字节码指令进行重写,将Bytecodes::_fast_bgetfield的Opcode存储到%ecx中0x00007fffe10203ac: mov    $0xcc,%ecx// 将Bytecodes::_fast_bgetfield的Opcode更新到字节码指令的操作码0x00007fffe10203b1: mov    %cl,0x0(%r13)// 跳转到---- Done ----0x00007fffe10203b5: jmpq   0x00007fffe102050f---- notByte ----0x00007fffe10203ba: cmp    $0x7,%eax0x00007fffe10203bd: jne    0x00007fffe102045d  // 跳转到notObj// atos// 调用MacroAssembler::load_heap_oop()函数生成如下代码0x00007fffe10203c3: mov    (%rcx,%rbx,1),%eax// ... 省略部分代码// 结束MacroAssembler::load_heap_oop()函数的调用0x00007fffe102044e: push   %rax// 重写字节码指令为Bytecodes::_fast_agetfield0x00007fffe102044f: mov    $0xcb,%ecx0x00007fffe1020454: mov    %cl,0x0(%r13)0x00007fffe1020458: jmpq   0x00007fffe102050f// -- notObj --0x00007fffe102045d: cmp    $0x3,%eax0x00007fffe1020460: jne    0x00007fffe1020478 // 跳转到notInt// itos0x00007fffe1020466: mov    (%rcx,%rbx,1),%eax0x00007fffe1020469: push   %rax// 重写字节码指令o Bytecodes::_fast_igetfield0x00007fffe102046a: mov    $0xd0,%ecx0x00007fffe102046f: mov    %cl,0x0(%r13)0x00007fffe1020473: jmpq   0x00007fffe102050f// --- notInt ----0x00007fffe1020478: cmp    $0x1,%eax0x00007fffe102047b: jne    0x00007fffe1020494 // 跳转到notChar// ctos0x00007fffe1020481: movzwl (%rcx,%rbx,1),%eax0x00007fffe1020485: push   %rax// 重写字节码指令为Bytecodes::_fast_cgetfield0x00007fffe1020486: mov    $0xcd,%ecx0x00007fffe102048b: mov    %cl,0x0(%r13)0x00007fffe102048f: jmpq   0x00007fffe102050f// ---- notChar ----0x00007fffe1020494: cmp    $0x2,%eax0x00007fffe1020497: jne    0x00007fffe10204b0 // 跳转到notShort// stos0x00007fffe102049d: movswl (%rcx,%rbx,1),%eax0x00007fffe10204a1: push   %rax// 重写字节码指令为Bytecodes::_fast_sgetfield0x00007fffe10204a2: mov    $0xd2,%ecx0x00007fffe10204a7: mov    %cl,0x0(%r13)0x00007fffe10204ab: jmpq   0x00007fffe102050f// ---- notShort ----0x00007fffe10204b0: cmp    $0x4,%eax0x00007fffe10204b3: jne    0x00007fffe10204d3 // 跳转到notLong// ltos0x00007fffe10204b9: mov    (%rcx,%rbx,1),%rax0x00007fffe10204bd: sub    $0x10,%rsp0x00007fffe10204c1: mov    %rax,(%rsp)// 重写字节码指令为Bytecodes::_fast_lgetfield,0x00007fffe10204c5: mov    $0xd1,%ecx0x00007fffe10204ca: mov    %cl,0x0(%r13)0x00007fffe10204ce: jmpq   0x00007fffe102050f// ---- notLong ----0x00007fffe10204d3: cmp    $0x5,%eax0x00007fffe10204d6: jne    0x00007fffe10204f8 // 跳转到notFloat// ftos0x00007fffe10204dc: vmovss (%rcx,%rbx,1),%xmm00x00007fffe10204e1: sub    $0x8,%rsp0x00007fffe10204e5: vmovss %xmm0,(%rsp)// 重写字节码指令为Bytecodes::_fast_fgetfield0x00007fffe10204ea: mov    $0xcf,%ecx0x00007fffe10204ef: mov    %cl,0x0(%r13)0x00007fffe10204f3: jmpq   0x00007fffe102050f// ---- notFloat ----0x00007fffe10204f8: vmovsd (%rcx,%rbx,1),%xmm00x00007fffe10204fd: sub    $0x10,%rsp0x00007fffe1020501: vmovsd %xmm0,(%rsp)0x00007fffe1020506: mov    $0xce,%ecx0x00007fffe102050b: mov    %cl,0x0(%r13)// -- Done --    

我们需要介绍一下虚拟机内部的一些自定义指令,这些自定义指令的模板如下:

// JVM bytecodes

def(Bytecodes::_fast_agetfield      , ubcp|____|____|____, atos, atos, fast_accessfield    ,  atos        );

def(Bytecodes::_fast_bgetfield      , ubcp|____|____|____, atos, itos, fast_accessfield    ,  itos        );

def(Bytecodes::_fast_cgetfield      , ubcp|____|____|____, atos, itos, fast_accessfield    ,  itos        );

def(Bytecodes::_fast_dgetfield      , ubcp|____|____|____, atos, dtos, fast_accessfield    ,  dtos        );

def(Bytecodes::_fast_fgetfield      , ubcp|____|____|____, atos, ftos, fast_accessfield    ,  ftos        );

def(Bytecodes::_fast_igetfield      , ubcp|____|____|____, atos, itos, fast_accessfield    ,  itos        );

def(Bytecodes::_fast_lgetfield      , ubcp|____|____|____, atos, ltos, fast_accessfield    ,  ltos        );

def(Bytecodes::_fast_sgetfield      , ubcp|____|____|____, atos, itos, fast_accessfield    ,  itos        );

以_fast_agetfield内部定义的字节码指令为例为来,生成函数为TemplateTable::fast_accessfield()函数,汇编代码如下:

0x00007fffe101e4e1: movzwl 0x1(%r13),%ebx0x00007fffe101e4e6: mov    -0x28(%rbp),%rcx0x00007fffe101e4ea: shl    $0x2,%ebx// 计算%rcx+%rdx*8+0x20,获取ConstantPoolCacheEntry[_indices,_f1,_f2,_flags]中的_f2// 因为ConstantPoolCache的大小为0x16字节,%rcx+0x20定位到第一个ConstantPoolCacheEntry的开始位置

// %rdx*8算出来的是相对于第一个ConstantPoolCacheEntry的字节偏移0x00007fffe101e4ed: mov    0x20(%rcx,%rbx,8),%rbx// 检查空异常0x00007fffe101e4f2: cmp    (%rax),%rax// %rax中存储的是objectref,也就是要从这个实例中获取字段的值,通过偏移%rbx后就

// 能获取到偏移的值,然后加载到%eax0x00007fffe101e4f5: mov    (%rax,%rbx,1),%eax  

其它的字节码指令类似,这里不再过多介绍。从这里可以看出,我们不需要再执行getfield对应的那些汇编指令,只执行_fast开头的指令即可,这些指令比起getfield指令来说简化了很多,大大提高了解释执行的速度。  

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

之前已经介绍了getstatic与getfield指令的汇编代码执行逻辑,这一篇介绍putstatic指令的执行逻辑,putfield将不再介绍,大家可以自己去研究,相信大家有这个实力。

putstatic指令为指定类的静态域赋值。字节码指令的格式如下:

putstatic indexbyte1 indexbyte2

无符号数indexbyte1和indexbyte2构建为(indexbyte1<<8)|indexbyte2,该索引所指向的运行时常量池项应当是一个字段的符号引用。

指令的模板定义如下:

def(Bytecodes::_putstatic           , ubcp|____|clvm|____, vtos, vtos, putstatic           , f2_byte      );  

生成函数为putstatic(),函数的实现如下:

void TemplateTable::putstatic(int byte_no) {  putfield_or_static(byte_no, false);

}

调用TemplateTable::putfield_or_static()函数生成的机器指令对应的汇编代码如下:

0x00007fffe101ff90: movzwl 0x1(%r13),%edx0x00007fffe101ff95: mov    -0x28(%rbp),%rcx0x00007fffe101ff99: shl    $0x2,%edx0x00007fffe101ff9c: mov    0x10(%rcx,%rdx,8),%ebx0x00007fffe101ffa0: shr    $0x18,%ebx0x00007fffe101ffa3: and    $0xff,%ebx// 是否已经对putstatic指令进行了连接,如果已经连接,则跳转到resolved0x00007fffe101ffa9: cmp    $0xb3,%ebx0x00007fffe101ffaf: je     0x00007fffe102004e

调用TemplateTable::resolve_cache_and_index()函数生成如下汇编代码:

// 执行到这里,说明字段还没有连接0x00007fffe101ffb5: mov    $0xb3,%ebx



// 调用MacroAssembler::call_VM()函数生成如下代码,

// 用来执行InterpreterRuntime::resolve_get_put()函数0x00007fffe101ffba: callq  0x00007fffe101ffc40x00007fffe101ffbf: jmpq   0x00007fffe10200420x00007fffe101ffc4: mov    %rbx,%rsi0x00007fffe101ffc7: lea    0x8(%rsp),%rax0x00007fffe101ffcc: mov    %r13,-0x38(%rbp)0x00007fffe101ffd0: mov    %r15,%rdi0x00007fffe101ffd3: mov    %rbp,0x200(%r15)0x00007fffe101ffda: mov    %rax,0x1f0(%r15)0x00007fffe101ffe1: test   $0xf,%esp0x00007fffe101ffe7: je     0x00007fffe101ffff0x00007fffe101ffed: sub    $0x8,%rsp0x00007fffe101fff1: callq  0x00007ffff66b567c0x00007fffe101fff6: add    $0x8,%rsp0x00007fffe101fffa: jmpq   0x00007fffe10200040x00007fffe101ffff: callq  0x00007ffff66b567c0x00007fffe1020004: movabs $0x0,%r100x00007fffe102000e: mov    %r10,0x1f0(%r15)0x00007fffe1020015: movabs $0x0,%r100x00007fffe102001f: mov    %r10,0x200(%r15)0x00007fffe1020026: cmpq   $0x0,0x8(%r15)0x00007fffe102002e: je     0x00007fffe10200390x00007fffe1020034: jmpq   0x00007fffe10004200x00007fffe1020039: mov    -0x38(%rbp),%r130x00007fffe102003d: mov    -0x30(%rbp),%r140x00007fffe1020041: retq   





0x00007fffe1020042: movzwl 0x1(%r13),%edx0x00007fffe1020047: mov    -0x28(%rbp),%rcx0x00007fffe102004b: shl    $0x2,%edx

接下来生成的汇编代码如下:

// ---- resolved ----// 执行如下代码时,表示字段已经连接完成0x00007fffe102004e: mov    0x20(%rcx,%rdx,8),%rbx0x00007fffe1020053: mov    0x28(%rcx,%rdx,8),%eax0x00007fffe1020057: mov    0x18(%rcx,%rdx,8),%rcx0x00007fffe102005c: mov    0x70(%rcx),%rcx0x00007fffe1020060: mov    %eax,%edx// 将_flags向右移动21位,判断是否有volatile关键字0x00007fffe1020062: shr    $0x15,%edx0x00007fffe1020065: and    $0x1,%edx// 将_flags向右移动28位,剩下TosState0x00007fffe1020068: shr    $0x1c,%eax// 如果不为btos,则跳转到notByte0x00007fffe102006b: and    $0xf,%eax0x00007fffe102006e: jne    0x00007fffe1020083// btos// 将栈顶的值存储到%eax中,这个值会写入到对应的字段中0x00007fffe1020074: mov    (%rsp),%eax0x00007fffe1020077: add    $0x8,%rsp// %rcx为_java_mirror,%rbx为_f2,表示域在类中的偏移0x00007fffe102007b: mov    %al,(%rcx,%rbx,1)0x00007fffe102007e: jmpq   0x00007fffe10201be  // 跳转到Done// -- notByte --// 如果不为atos,则跳转到notObj0x00007fffe1020083: cmp    $0x7,%eax0x00007fffe1020086: jne    0x00007fffe1020130// atos// 将栈顶的值弹出到%rax中,这个值将用来更新对应字段的值0x00007fffe102008c: pop    %rax// ...// 将值更新到对应的字段上0x00007fffe1020115: mov    %eax,(%rcx,%rbx,1)// 其中的0x9是CardTableModRefBS::card_shift,shr表示逻辑右移,由于%rcx指向的是// java.lang.Class实例的首地址,向右移后%rcx就算出了卡表的索引0x00007fffe1020118: shr    $0x9,%rcx// 地址常量$0x7fffe07ff000表示卡表的基地址0x00007fffe102011c: movabs $0x7fffe07ff000,%r10 

// 将对应的卡表项标记为脏,其中常量0x0就表示是脏卡0x00007fffe1020126: movb $0x0,(%r10,%rcx,1) 

0x00007fffe102012b: jmpq 

0x00007fffe10201be // 跳转到Done// ---- notObj ----// 如果不为itos,那么跳转到notInt0x00007fffe1020130: cmp    $0x3,%eax0x00007fffe1020133: jne    0x00007fffe1020148// itos0x00007fffe1020139: mov    (%rsp),%eax// 如果不为ctos,则跳转到notChar0x00007fffe102013c: add    $0x8,%rsp0x00007fffe1020140: mov    %eax,(%rcx,%rbx,1)0x00007fffe1020143: jmpq   0x00007fffe10201be   // 跳转到Done0x00007fffe1020148: cmp    $0x1,%eax0x00007fffe102014b: jne    0x00007fffe1020161// ctos0x00007fffe1020151: mov    (%rsp),%eax0x00007fffe1020154: add    $0x8,%rsp0x00007fffe1020158: mov    %ax,(%rcx,%rbx,1)0x00007fffe102015c: jmpq   0x00007fffe10201be  // 跳转到Done0x00007fffe1020161: cmp    $0x2,%eax0x00007fffe1020164: jne    0x00007fffe102017a// stos0x00007fffe102016a: mov    (%rsp),%eax0x00007fffe102016d: add    $0x8,%rsp0x00007fffe1020171: mov    %ax,(%rcx,%rbx,1)0x00007fffe1020175: jmpq   0x00007fffe10201be  // 跳转到Done0x00007fffe102017a: cmp    $0x4,%eax0x00007fffe102017d: jne    0x00007fffe1020194// ltos0x00007fffe1020183: mov    (%rsp),%rax0x00007fffe1020187: add    $0x10,%rsp0x00007fffe102018b: mov    %rax,(%rcx,%rbx,1)0x00007fffe102018f: jmpq   0x00007fffe10201be  // 跳转到Done0x00007fffe1020194: cmp    $0x5,%eax0x00007fffe1020197: jne    0x00007fffe10201b0// ftos0x00007fffe102019d: vmovss (%rsp),%xmm00x00007fffe10201a2: add    $0x8,%rsp0x00007fffe10201a6: vmovss %xmm0,(%rcx,%rbx,1)0x00007fffe10201ab: jmpq   0x00007fffe10201be   // 跳转到Done// dtos0x00007fffe10201b0: vmovsd (%rsp),%xmm00x00007fffe10201b5: add    $0x10,%rsp0x00007fffe10201b9: vmovsd %xmm0,(%rcx,%rbx,1)// ---- Done ----0x00007fffe10201be: test   %edx,%edx0x00007fffe10201c0: je     0x00007fffe10201cb0x00007fffe10201c6: lock addl $0x0,(%rsp)// ---- notVolatile ----   

在如上代码中,最值得关注的2个点如下:

(1)更新引用字段时,通过屏障将对应的卡表项标记为脏,这样可在GC过程中扫描脏卡就可将活跃对象标记出来而不会造成遗漏;

(2)当字段有volatile关键字修饰时,需要填写lock指令前缀,这个前缀在之前介绍x86-64机器指令时没有介绍过,这里摘抄一下别人对此指令的介绍:

Intel手册对 lock 前缀的说明如下:

  1. 确保被修饰指令执行的原子性;
  2. 禁止该指令与前面和后面的读写指令重排序;
  3. 指令执行完后把写缓冲区的所有数据刷新到内存中(这样这个指令之前的其他修改对所有处理器可见)。

在所有的 X86 CPU 上都具有锁定一个特定内存地址的能力,当这个特定内存地址被锁定后,它就可以阻止其他的系统总线读取或修改这个内存地址。这种能力是通过 lock 指令前缀再加上下面的汇编指令来实现的。当使用 lock 指令前缀时,它会使 CPU 宣告一个 lock# 信号,这样就能确保在多处理器系统或多线程竞争的环境下互斥地使用这个内存地址。当指令执行完毕,这个锁定动作也就会消失。

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

操作数栈管理相关的字节码指令如下表所示。

   
0x57pop将栈顶数值弹出 (数值不能是long或double类型的)
0x58pop2将栈顶的一个(long或double类型的)或两个数值弹出(其它)
0x59dup复制栈顶数值并将复制值压入栈顶
0x5adup_x1复制栈顶数值并将两个复制值压入栈顶
0x5bdup_x2复制栈顶数值并将三个(或两个)复制值压入栈顶
0x5cdup2复制栈顶一个(long或double类型的)或两个(其它)数值并将复制值压入栈顶
0x5ddup2_x1dup_x1 指令的双倍版本
0x5edup2_x2dup_x2 指令的双倍版本
0x5fswap将栈最顶端的两个数值互换(数值不能是long或double类型的)

字节码指令对应的模板定义如下:

def(Bytecodes::_pop         , ____|____|____|____, vtos, vtos, pop         ,  _           );

def(Bytecodes::_pop2        , ____|____|____|____, vtos, vtos, pop2        ,  _           );

def(Bytecodes::_dup         , ____|____|____|____, vtos, vtos, dup         ,  _           );

def(Bytecodes::_dup_x1      , ____|____|____|____, vtos, vtos, dup_x1      ,  _           );

def(Bytecodes::_dup_x2      , ____|____|____|____, vtos, vtos, dup_x2      ,  _           );

def(Bytecodes::_dup2        , ____|____|____|____, vtos, vtos, dup2        ,  _           );

def(Bytecodes::_dup2_x1     , ____|____|____|____, vtos, vtos, dup2_x1     ,  _           );

def(Bytecodes::_dup2_x2     , ____|____|____|____, vtos, vtos, dup2_x2     ,  _           );

def(Bytecodes::_swap        , ____|____|____|____, vtos, vtos, swap        ,  _           );

pop指令将栈顶数值弹出。对应的汇编代码如下:

add    $0x8,%rsp

pop2指令将栈顶数值弹出。对应的汇编代码如下:

add    $0x10,%rsp

dup指令复制栈顶数值并将复制值压入栈顶。对应的汇编代码如下:

mov    (%rsp),%raxpush   %rax

swap指令将栈最顶端的两个数值互换(数值不能是long或double类型的)。对应的汇编代码如下:

mov    0x8(%rsp),%rcxmov    (%rsp),%raxmov    %rcx,(%rsp)

mov    %rax,0x8(%rsp)

指令的执行逻辑比较简单,这里不再过多介绍。

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

控制转移相关的字节码指令如下表所示。 

   
0x99ifeq当栈顶int型数值等于0时跳转
0x9aifne当栈顶int型数值不等于0时跳转
0x9biflt当栈顶int型数值小于0时跳转
0x9cifge当栈顶int型数值大于等于0时跳转
0x9difgt当栈顶int型数值大于0时跳转
0x9eifle当栈顶int型数值小于等于0时跳转
0x9fif_icmpeq比较栈顶两int型数值大小,当结果等于0时跳转
0xa0if_icmpne比较栈顶两int型数值大小,当结果不等于0时跳转
0xa1if_icmplt比较栈顶两int型数值大小,当结果小于0时跳转
0xa2if_icmpge比较栈顶两int型数值大小,当结果大于等于0时跳转
0xa3if_icmpgt比较栈顶两int型数值大小,当结果大于0时跳转
0xa4if_icmple比较栈顶两int型数值大小,当结果小于等于0时跳转
0xa5if_acmpeq比较栈顶两引用型数值,当结果相等时跳转
0xa6if_acmpne比较栈顶两引用型数值,当结果不相等时跳转
0xa7goto无条件跳转
0xa8jsr跳转至指定16位offset位置,并将jsr下一条指令地址压入栈顶
0xa9ret返回至本地变量指令的index的指令位置(一般与jsr或jsr_w联合使用)
0xaatableswitch用于switch条件跳转,case值连续(可变长度指令)
0xablookupswitch用于switch条件跳转,case值不连续(可变长度指令)
0xacireturn从当前方法返回int
0xadlreturn从当前方法返回long
0xaefreturn从当前方法返回float
0xafdreturn从当前方法返回double
0xb0areturn从当前方法返回对象引用
0xb1return从当前方法返回void
0xc6ifnull为null时跳转
0xc7ifnonnull不为null时跳转
0xc8goto_w无条件跳转(宽索引)
0xc9jsr_w跳转至指定32位offset位置,并将jsr_w下一条指令地址压入栈顶

 

模板定义如下:

def(Bytecodes::_ifeq                , ubcp|____|clvm|____, itos, vtos, if_0cmp             , equal        );

def(Bytecodes::_ifne                , ubcp|____|clvm|____, itos, vtos, if_0cmp             , not_equal    );

def(Bytecodes::_iflt                , ubcp|____|clvm|____, itos, vtos, if_0cmp             , less         );

def(Bytecodes::_ifge                , ubcp|____|clvm|____, itos, vtos, if_0cmp             , greater_equal);

def(Bytecodes::_ifgt                , ubcp|____|clvm|____, itos, vtos, if_0cmp             , greater      );

def(Bytecodes::_ifle                , ubcp|____|clvm|____, itos, vtos, if_0cmp             , less_equal   );

def(Bytecodes::_if_icmpeq           , ubcp|____|clvm|____, itos, vtos, if_icmp             , equal        );

def(Bytecodes::_if_icmpne           , ubcp|____|clvm|____, itos, vtos, if_icmp             , not_equal    );

def(Bytecodes::_if_icmplt           , ubcp|____|clvm|____, itos, vtos, if_icmp             , less         );

def(Bytecodes::_if_icmpge           , ubcp|____|clvm|____, itos, vtos, if_icmp             , greater_equal);

def(Bytecodes::_if_icmpgt           , ubcp|____|clvm|____, itos, vtos, if_icmp             , greater      );

def(Bytecodes::_if_icmple           , ubcp|____|clvm|____, itos, vtos, if_icmp             , less_equal   );

def(Bytecodes::_if_acmpeq           , ubcp|____|clvm|____, atos, vtos, if_acmp             , equal        );

def(Bytecodes::_if_acmpne           , ubcp|____|clvm|____, atos, vtos, if_acmp             , not_equal    );

def(Bytecodes::_goto                , ubcp|disp|clvm|____, vtos, vtos, _goto               ,  _           );

def(Bytecodes::_jsr                 , ubcp|disp|____|____, vtos, vtos, jsr                 ,  _           ); // result is not an oop, so do not transition to atos

def(Bytecodes::_ret                 , ubcp|disp|____|____, vtos, vtos, ret                 ,  _           );

def(Bytecodes::_tableswitch         , ubcp|disp|____|____, itos, vtos, tableswitch         ,  _           );

def(Bytecodes::_lookupswitch        , ubcp|disp|____|____, itos, itos, lookupswitch        ,  _           );

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::_ifnull              , ubcp|____|clvm|____, atos, vtos, if_nullcmp          , equal        );

def(Bytecodes::_ifnonnull           , ubcp|____|clvm|____, atos, vtos, if_nullcmp          , not_equal    );

def(Bytecodes::_goto_w              , ubcp|____|clvm|____, vtos, vtos, goto_w              ,  _           );

def(Bytecodes::_jsr_w               , ubcp|____|____|____, vtos, vtos, jsr_w               ,  _           );

下面介绍几个典型指令的汇编实现。

1、goto指令

goto字节码指令的生成函数为TemplateTable::_goto(),生成的汇编代码如下:(在生成代码时添加命令-Xint   -XX:-ProfileInterpreter,这样可排除生成一些不必要的指令)

// Method*保存到%rcx中0x00007fffe1019df0: mov    -0x18(%rbp),%rcx// 将goto后的index(2个字节)存储到%edx中0x00007fffe1019df4: movswl 0x1(%r13),%edx0x00007fffe1019df9: bswap  %edx// 算术右移指令0x00007fffe1019dfb: sar    $0x10,%edx// 将一个双字符号扩展后送到一个四字地址中0x00007fffe1019dfe: movslq %edx,%rdx// 将当前字节码地址加上rdx保存的偏移量,计算跳转的目标地址0x00007fffe1019e01: add    %rdx,%r13// %r13已经变成目标跳转地址,这里是加载跳转地址的第一个字节码到rbx中0x00007fffe1019e04: movzbl 0x0(%r13),%ebx// continue with the bytecode @ target// eax: return bci for jsr's, unused otherwise// ebx: target bytecode// r13: target bcp// 开始执行跳转地址处的字节码,其中的常量地址为// TemplateInterpreter::_active_table的、栈顶缓存状态为vtos的首地址0x00007fffe1019e09: movabs $0x7ffff73ba4a0,%r100x00007fffe1019e13: jmpq   *(%r10,%rbx,8)

其实goto指令实际上生成的汇编代码要比上面的代码多的多,因为goto指令是一个分支指令,其中会做一些性能统计以辅助进行编译优化,而且goto如果是在循环中的话,还可能会涉及到栈上替换的技术,所以后面我们在介绍到对应的技术点时再详细介绍goto指令的其它一些汇编逻辑。 

2、ifeq、ifne等指令  

现在ifeq、ifne等指令的生成函数为TemplateTable::if_0cmp()。ifeq字节码指令表示栈顶值与零值比较,当且仅当栈顶的int类型的值为0时,比较结果为真。对应的汇编代码如下: 

0x00007fffe10196c7: test   %eax,%eax// 当栈顶缓存%eax不为0时,直接跳到not_taken0x00007fffe10196c9: jne    0x00007fffe10196f6// 调用TemplateTable::branch(false,false)函数生成的汇编代码// 将当前栈帧中保存的Method* 拷贝到rcx中0x00007fffe10196cf: mov    -0x18(%rbp),%rcx// 将当前字节码位置往后偏移1字节处开始的2字节数据读取到edx中0x00007fffe10196d3: movswl 0x1(%r13),%edx// 将%edx中的值字节次序变反0x00007fffe10196d8: bswap  %edx// 将edx中的值右移16位,上述两步就是为了计算跳转分支的偏移量0x00007fffe10196da: sar    $0x10,%edx// 将edx中的数据从2字节扩展成4字节0x00007fffe10196dd: movslq %edx,%rdx// 将当前字节码地址加上rdx保存的偏移量,计算跳转的目标地址0x00007fffe10196e0: add    %rdx,%r13// r13已经变成目标跳转地址,这里是加载跳转地址的第一个字节码到ebx中0x00007fffe10196e3: movzbl 0x0(%r13),%ebx// 开始执行跳转地址处的字节码,其中的常量地址为// TemplateInterpreter::_active_table的、栈顶缓存状态为vtos的首地址0x00007fffe10196e8: movabs $0x7ffff73ba4a0,%r100x00007fffe10196f2: jmpq   *(%r10,%rbx,8)// -- not_taken -- 

类似的指令实现逻辑也高度类似,大家有兴趣可自行研究。 

3、lookupswitch、tableswitch等指令 

lookupswitch指令根据键值在跳转表中寻找配对的分支并跳转,具体的格式如下图所示。

 

这是一条变长指令并且要求所有的操作数都4字节对齐,所以紧跟在lookupswitch指令之后可能会有0到3个字节作为空白填充,而后面的default、npairs等都用4字节来表示,从当前方法开始(第一条字节码指令)计算的地址,即紧随空白填充的是一系列32位有符号整数值,包括默认跳转地址default、匹配坐标的数量npairs以及npairs组匹配坐标。其中npairs的值应当大于或等于0,每一组匹配坐标都包含了一个整数值match以及一个有符号32位偏移量offset。上述所有的32位有符号数值都是通过以下方式计算得到:

(byte1<<24)|(byte2<<24)|(byte3<<24)|byte4

tableswitch指令根据键值在跳转表中寻找配对的分支并跳转,具体的格式如下图所示。

这是一条变长指令并且要求所有的操作数都4字节对齐,所以紧跟在lookupswitch指令之后可能会有0到3个字节作为空白填充,而后面的default、lowbyte、highbyte等用4字节来表示,从当前方法开始(第一条字节码指令)计算的地址,即紧随空白填充的是一系列32位有符号整数值,包括默认跳转地址default、高位值high以及低位值low,在此之后是high-low+1个有符号32位偏移offset。上述所有的32位有符号数值都是通过以下方式计算得到:

(byte1<<24)|(byte2<<24)|(byte3<<24)|byte4

生成函数为TemplateTable::tableswitch(),生成的汇编如下:

// align r13,按照4字节对齐0x00007fffe1019fa7: lea    0x4(%r13),%rbx0x00007fffe1019fab: and    $0xfffffffffffffffc,%rbx// load lo & hi0x00007fffe1019faf: mov    0x4(%rbx),%ecx0x00007fffe1019fb2: mov    0x8(%rbx),%edx0x00007fffe1019fb5: bswap  %ecx0x00007fffe1019fb7: bswap  %edx// check against lo & hi// %ecx中存储的是lowbyte0x00007fffe1019fb9: cmp    %ecx,%eax// 如果比低位值还低,则跳转到default_case0x00007fffe1019fbb: jl     0x00007fffe1019feb // %edx中存储的是highbyte0x00007fffe1019fc1: cmp    %edx,%eax// 如果比高位值还高,则跳转到default_case0x00007fffe1019fc3: jg     0x00007fffe1019feb// lookup dispatch offset0x00007fffe1019fc9: sub    %ecx,%eax// %rbx中存储的是对齐后的字节码指令地址,%rax中存储的是栈顶缓存值0x00007fffe1019fcb: mov    0xc(%rbx,%rax,4),%edx// -- continue_execution --// continue execution0x00007fffe1019fcf: bswap  %edx0x00007fffe1019fd1: movslq %edx,%rdx0x00007fffe1019fd4: movzbl 0x0(%r13,%rdx,1),%ebx0x00007fffe1019fda: add    %rdx,%r130x00007fffe1019fdd: movabs $0x7ffff73ba4a0,%r100x00007fffe1019fe7: jmpq   *(%r10,%rbx,8)// -- default_case --// handle default0x00007fffe1019feb: mov (%rbx),%edx 

// 跳转到continue_execution0x00007fffe1019fed: jmp 0x00007fffe1019fcf

 

  • 0
    感动
  • 0
    路过
  • 0
    高兴
  • 0
    难过
  • 0
    搞笑
  • 0
    无聊
  • 0
    愤怒
  • 0
    同情
热度排行
友情链接