第22篇-虚拟机字节码之运算指令
虚拟机规范中与运算相关的字节码指令如下表所示。
0x60 | iadd | 将栈顶两int型数值相加并将结果压入栈顶 |
0x61 | ladd | 将栈顶两long型数值相加并将结果压入栈顶 |
0x62 | fadd | 将栈顶两float型数值相加并将结果压入栈顶 |
0x63 | dadd | 将栈顶两double型数值相加并将结果压入栈顶 |
0x64 | isub | 将栈顶两int型数值相减并将结果压入栈顶 |
0x65 | lsub | 将栈顶两long型数值相减并将结果压入栈顶 |
0x66 | fsub | 将栈顶两float型数值相减并将结果压入栈顶 |
0x67 | dsub | 将栈顶两double型数值相减并将结果压入栈顶 |
0x68 | imul | 将栈顶两int型数值相乘并将结果压入栈顶 |
0x69 | lmul | 将栈顶两long型数值相乘并将结果压入栈顶 |
0x6a | fmul | 将栈顶两float型数值相乘并将结果压入栈顶 |
0x6b | dmul | 将栈顶两double型数值相乘并将结果压入栈顶 |
0x6c | idiv | 将栈顶两int型数值相除并将结果压入栈顶 |
0x6d | ldiv | 将栈顶两long型数值相除并将结果压入栈顶 |
0x6e | fdiv | 将栈顶两float型数值相除并将结果压入栈顶 |
0x6f | ddiv | 将栈顶两double型数值相除并将结果压入栈顶 |
0x70 | irem | 将栈顶两int型数值作取模运算并将结果压入栈顶 |
0x71 | lrem | 将栈顶两long型数值作取模运算并将结果压入栈顶 |
0x72 | frem | 将栈顶两float型数值作取模运算并将结果压入栈顶 |
0x73 | drem | 将栈顶两double型数值作取模运算并将结果压入栈顶 |
0x74 | ineg | 将栈顶int型数值取负并将结果压入栈顶 |
0x75 | lneg | 将栈顶long型数值取负并将结果压入栈顶 |
0x76 | fneg | 将栈顶float型数值取负并将结果压入栈顶 |
0x77 | dneg | 将栈顶double型数值取负并将结果压入栈顶 |
0x78 | ishl | 将int型数值左移位指定位数并将结果压入栈顶 |
0x79 | lshl | 将long型数值左移位指定位数并将结果压入栈顶 |
0x7a | ishr | 将int型数值右(符号)移位指定位数并将结果压入栈顶 |
0x7b | lshr | 将long型数值右(符号)移位指定位数并将结果压入栈顶 |
0x7c | iushr | 将int型数值右(无符号)移位指定位数并将结果压入栈顶 |
0x7d | lushr | 将long型数值右(无符号)移位指定位数并将结果压入栈顶 |
0x7e | iand | 将栈顶两int型数值作“按位与”并将结果压入栈顶 |
0x7f | land | 将栈顶两long型数值作“按位与”并将结果压入栈顶 |
0x80 | ior | 将栈顶两int型数值作“按位或”并将结果压入栈顶 |
0x81 | lor | 将栈顶两long型数值作“按位或”并将结果压入栈顶 |
0x82 | ixor | 将栈顶两int型数值作“按位异或”并将结果压入栈顶 |
0x83 | lxor | 将栈顶两long型数值作“按位异或”并将结果压入栈顶 |
0x84 | iinc | 将指定int型变量增加指定值(i++、i--、i+=2) |
0x94 | lcmp | 比较栈顶两long型数值大小,并将结果(1、0或-1)压入栈顶 |
0x95 | fcmpl | 比较栈顶两float型数值大小,并将结果(1、0或-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶 |
0x96 | fcmpg | 比较栈顶两float型数值大小,并将结果(1、0或-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶 |
0x97 | dcmpl | 比较栈顶两double型数值大小,并将结果(1、0或-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶 |
0x98 | dcmpg | 比较栈顶两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),%edx add $0x8,%rsp add %edx,%eax
将栈顶与栈顶中缓存的%eax相加后的结果存储到%eax中。
2、isub指令
isub指令生成的汇编代码如下:
mov %eax,%edx mov (%rsp),%eax add $0x8,%rsp sub %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,%ecx 0x00007fffe1019709: mov (%rsp),%eax 0x00007fffe101970c: add $0x8,%rsp // 测试一下被除数是否为0x80000000,如果不是,就跳转到normal_case 0x00007fffe1019710: cmp $0x80000000,%eax 0x00007fffe1019716: jne 0x00007fffe1019727 // 被除数是0x80000000,而除数如果是-1的话,则跳转到special_case 0x00007fffe101971c: xor %edx,%edx 0x00007fffe101971e: cmp $0xffffffff,%ecx 0x00007fffe1019721: je 0x00007fffe101972a // -- normal_case -- // cltd将eax寄存器中的数据符号扩展到edx:eax,具体就是 // 把eax的32位整数扩展为64位,高32位用eax的符号位填充保存到edx 0x00007fffe1019727: 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),%rdx 0x00007fffe101a6cc: add $0x10,%rsp // cmp指令描述如下: // 第1操作数<第2操作数时,ZF=0 // 第1操作数=第2操作数时,ZF=1 // 第1操作数>第2操作数时,ZF=0 0x00007fffe101a6d0: cmp %rax,%rdx 0x00007fffe101a6d3: mov $0xffffffff,%eax // 将-1移到%eax中 // 如果第1操作数小于第2操作数就跳转到done 0x00007fffe101a6d8: jl 0x00007fffe101a6e0 // cmp指令执行后,执行setne指令就能获取比较的结果 // 根据eflags中的状态标志(CF,SF,OF,ZF和PF)将目标操作数设置为0或1 0x00007fffe101a6da: setne %al 0x00007fffe101a6dd: movzbl %al,%eax // -- done --
如上汇编代码的逻辑非常简单,这里不再介绍。
关于其它字节码指令的逻辑也相对简单,有兴趣的可自行研究。
第23篇-虚拟机字节码指令之类型转换
Java虚拟机规范中定义的类型转换相关的字节码指令如下表所示。
0x85 | i2l | 将栈顶int型数值强制转换成long型数值并将结果压入栈顶 |
0x86 | i2f | 将栈顶int型数值强制转换成float型数值并将结果压入栈顶 |
0x87 | i2d | 将栈顶int型数值强制转换成double型数值并将结果压入栈顶 |
0x88 | l2i | 将栈顶long型数值强制转换成int型数值并将结果压入栈顶 |
0x89 | l2f | 将栈顶long型数值强制转换成float型数值并将结果压入栈顶 |
0x8a | l2d | 将栈顶long型数值强制转换成double型数值并将结果压入栈顶 |
0x8b | f2i | 将栈顶float型数值强制转换成int型数值并将结果压入栈顶 |
0x8c | f2l | 将栈顶float型数值强制转换成long型数值并将结果压入栈顶 |
0x8d | f2d | 将栈顶float型数值强制转换成double型数值并将结果压入栈顶 |
0x8e | d2i | 将栈顶double型数值强制转换成int型数值并将结果压入栈顶 |
0x8f | d2l | 将栈顶double型数值强制转换成long型数值并将结果压入栈顶 |
0x90 | d2f | 将栈顶double型数值强制转换成float型数值并将结果压入栈顶 |
0x91 | i2b | 将栈顶int型数值强制转换成byte型数值并将结果压入栈顶 |
0x92 | i2c | 将栈顶int型数值强制转换成char型数值并将结果压入栈顶 |
0x93 | i2s | 将栈顶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进行比较,如果不相等,则跳转到L 0x00007fffe101918d: cmp $0x80000000,%eax 0x00007fffe1019193: jne 0x00007fffe10191bc // 如果栈顶指针已经按16字节对齐,则可直接调用调用SharedRuntime::f2i()函数,否则 // 将栈顶指令按16字节对齐后再调用 0x00007fffe1019199: test $0xf,%esp 0x00007fffe101919f: je 0x00007fffe10191b7 0x00007fffe10191a5: sub $0x8,%rsp // 调用SharedRuntime::f2i()函数 0x00007fffe10191a9: callq 0x00007ffff6a0f946 0x00007fffe10191ae: add $0x8,%rsp 0x00007fffe10191b2: 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虚拟机规范中定义的对象操作相关的字节码指令如下表所示。
0xb2 | getstatic | 获取指定类的静态域,并将其值压入栈顶 |
0xb3 | putstatic | 为指定的类的静态域赋值 |
0xb4 | getfield | 获取指定类的实例域,并将其值压入栈顶 |
0xb5 | putfield | 为指定的类的实例域赋值 |
0xbb | new | 创建一个对象,并将其引用值压入栈顶 |
0xbc | newarray | 创建一个指定原始类型(如int,、float,、char等)的数组,并将其引用值压入栈顶 |
0xbd | anewarray | 创建一个引用型(如类、接口或数组)的数组,并将其引用值压入栈顶 |
0xbe | arraylength | 获得数组的长度值并压入栈顶 |
0xc0 | checkcast | 检验类型转换,检验未通过将抛出ClassCastException |
0xc1 | instanceof | 检验对象是否是指定的类的实例,如果是将1压入栈顶,否则将0压入栈顶 |
0xc5 | multianewarray | 创建指定类型和指定维度的多维数组(执行该指令时,操作栈中必须包含各维度的长度值),并将其引用值压入栈顶 |
字节码指令的模板定义如下:
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的index 0x00007fffe101fd10: 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 bytecode 0x00007fffe101fd20: shr $0x10,%ebx // 获取set bytecode字段的值 0x00007fffe101fd23: and $0xff,%ebx // 0xb2是getstatic指令的Opcode,比较值,如果相等就说明已经连接,跳转到resolved 0x00007fffe101fd29: cmp $0xb2,%ebx 0x00007fffe101fd2f: 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 0x00007fffe101fd44 0x00007fffe101fd3f: jmpq 0x00007fffe101fdc2 0x00007fffe101fd44: mov %rbx,%rsi 0x00007fffe101fd47: lea 0x8(%rsp),%rax 0x00007fffe101fd4c: mov %r13,-0x38(%rbp) 0x00007fffe101fd50: mov %r15,%rdi 0x00007fffe101fd53: mov %rbp,0x200(%r15) 0x00007fffe101fd5a: mov %rax,0x1f0(%r15) 0x00007fffe101fd61: test $0xf,%esp 0x00007fffe101fd67: je 0x00007fffe101fd7f 0x00007fffe101fd6d: sub $0x8,%rsp 0x00007fffe101fd71: callq 0x00007ffff66b567c 0x00007fffe101fd76: add $0x8,%rsp 0x00007fffe101fd7a: jmpq 0x00007fffe101fd84 0x00007fffe101fd7f: callq 0x00007ffff66b567c 0x00007fffe101fd84: movabs $0x0,%r10 0x00007fffe101fd8e: mov %r10,0x1f0(%r15) 0x00007fffe101fd95: movabs $0x0,%r10 0x00007fffe101fd9f: mov %r10,0x200(%r15) 0x00007fffe101fda6: cmpq $0x0,0x8(%r15) 0x00007fffe101fdae: je 0x00007fffe101fdb9 0x00007fffe101fdb4: jmpq 0x00007fffe1000420 0x00007fffe101fdb9: mov -0x38(%rbp),%r13 0x00007fffe101fdbd: mov -0x30(%rbp),%r14 0x00007fffe101fdc1: 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的索引存储么%edx 0x00007fffe101fdc2: movzwl 0x1(%r13),%edx // 将ConstantPoolCache的首地址存储到%rcx 0x00007fffe101fdc7: 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位,剩下TosState 0x00007fffe101fde0: shr $0x1c,%eax 0x00007fffe101fde3: and $0xf,%eax // 如果不相等,说明TosState的值不为0,则跳转到notByte 0x00007fffe101fde6: jne 0x00007fffe101fdf6 // btos // btos的编号为0,代码执行到这里时,可能栈顶缓存要求是btos // %rcx中存储的是_java_mirror,%rbx中存储的是_f2,由于静态变量存储在_java_mirror中,所以要获取 // 对应的首地址并压入栈中 0x00007fffe101fdec: movsbl (%rcx,%rbx,1),%eax 0x00007fffe101fdf0: push %rax // 跳转到Done 0x00007fffe101fdf1: jmpq 0x00007fffe101ff0c // -- notByte -- // %eax中存储的是TosState,如果不为atos,则跳转到notObj 0x00007fffe101fdf6: cmp $0x7,%eax 0x00007fffe101fdf9: jne 0x00007fffe101fe90 // atos // %rcx中存储的是_java_mirror,%rbx中存储的是_f2, // 所以要获取静态变量的首地址并压入栈内 0x00007fffe101fdff: mov (%rcx,%rbx,1),%eax 0x00007fffe101fe02: push %r10 0x00007fffe101fe04: cmp 0x163a8d45(%rip),%r12 # 0x00007ffff73c8b50 0x00007fffe101fe0b: je 0x00007fffe101fe88 0x00007fffe101fe11: mov %rsp,-0x28(%rsp) 0x00007fffe101fe16: sub $0x80,%rsp 0x00007fffe101fe1d: 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,%rdi 0x00007fffe101fe71: movabs $0x7fffe101fe11,%rsi 0x00007fffe101fe7b: mov %rsp,%rdx 0x00007fffe101fe7e: and $0xfffffffffffffff0,%rsp 0x00007fffe101fe82: callq 0x00007ffff6872e3a 0x00007fffe101fe87: hlt 0x00007fffe101fe88: pop %r10 0x00007fffe101fe8a: push %rax 0x00007fffe101fe8b: jmpq 0x00007fffe101ff0c // -- notObj -- 0x00007fffe101fe90: cmp $0x3,%eax // 如果不为itos,则跳转到notInt 0x00007fffe101fe93: jne 0x00007fffe101fea2 // itos 0x00007fffe101fe99: mov (%rcx,%rbx,1),%eax 0x00007fffe101fe9c: push %rax // 跳转到Done 0x00007fffe101fe9d: jmpq 0x00007fffe101ff0c // -- notInt -- // 如果不为ctos,则跳转到notChar 0x00007fffe101fea2: cmp $0x1,%eax 0x00007fffe101fea5: jne 0x00007fffe101feb5 // ctos 0x00007fffe101feab: movzwl (%rcx,%rbx,1),%eax 0x00007fffe101feaf: push %rax // 跳转到Done 0x00007fffe101feb0: jmpq 0x00007fffe101ff0c // -- notChar -- // 如果不为stos,则跳转到notShort 0x00007fffe101feb5: cmp $0x2,%eax 0x00007fffe101feb8: jne 0x00007fffe101fec8 // stos 0x00007fffe101febe: movswl (%rcx,%rbx,1),%eax 0x00007fffe101fec2: push %rax // 跳转到done 0x00007fffe101fec3: jmpq 0x00007fffe101ff0c // -- notShort -- // 如果不为ltos,则跳转到notLong 0x00007fffe101fec8: cmp $0x4,%eax 0x00007fffe101fecb: jne 0x00007fffe101fee2 // ltos 0x00007fffe101fed1: mov (%rcx,%rbx,1),%rax 0x00007fffe101fed5: sub $0x10,%rsp 0x00007fffe101fed9: mov %rax,(%rsp) // 跳转到Done 0x00007fffe101fedd: jmpq 0x00007fffe101ff0c // -- notLong -- // 如果不为ftos,则跳转到notFloat 0x00007fffe101fee2: cmp $0x5,%eax 0x00007fffe101fee5: jne 0x00007fffe101fefe // ftos 0x00007fffe101feeb: vmovss (%rcx,%rbx,1),%xmm0 0x00007fffe101fef0: sub $0x8,%rsp 0x00007fffe101fef4: vmovss %xmm0,(%rsp) // 跳转到Done 0x00007fffe101fef9: jmpq 0x00007fffe101ff0c // -- notFloat -- 0x00007fffe101fefe: vmovsd (%rcx,%rbx,1),%xmm0 0x00007fffe101ff03: sub $0x10,%rsp 0x00007fffe101ff07: 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),%edx 0x00007fffe10202d5: mov -0x28(%rbp),%rcx 0x00007fffe10202d9: shl $0x2,%edx 0x00007fffe10202dc: mov 0x10(%rcx,%rdx,8),%ebx 0x00007fffe10202e0: shr $0x10,%ebx 0x00007fffe10202e3: and $0xff,%ebx // 0xb4是getfield指令的Opcode,如果相等,则说明已经连接,直接跳转到resolved 0x00007fffe10202e9: cmp $0xb4,%ebx 0x00007fffe10202ef: je 0x00007fffe102038e 0x00007fffe10202f5: mov $0xb4,%ebx // 省略通过调用MacroAssembler::call_VM()函数来执行 // InterpreterRuntime::resolve_get_put()函数的汇编代码 // ...
调用MacroAssembler::call_VM()函数生成如下代码,通过这些代码来执行InterpreterRuntime::resolve_get_put()函数。MacroAssembler::call_VM()函数的汇编在之前已经详细介绍过,这里不再介绍,直接给出汇编代码,如下:
0x00007fffe10202fa: callq 0x00007fffe1020304 0x00007fffe10202ff: jmpq 0x00007fffe1020382 0x00007fffe1020304: mov %rbx,%rsi 0x00007fffe1020307: lea 0x8(%rsp),%rax 0x00007fffe102030c: mov %r13,-0x38(%rbp) 0x00007fffe1020310: mov %r15,%rdi 0x00007fffe1020313: mov %rbp,0x200(%r15) 0x00007fffe102031a: mov %rax,0x1f0(%r15) 0x00007fffe1020321: test $0xf,%esp 0x00007fffe1020327: je 0x00007fffe102033f 0x00007fffe102032d: sub $0x8,%rsp 0x00007fffe1020331: callq 0x00007ffff66b567c 0x00007fffe1020336: add $0x8,%rsp 0x00007fffe102033a: jmpq 0x00007fffe1020344 0x00007fffe102033f: callq 0x00007ffff66b567c 0x00007fffe1020344: movabs $0x0,%r10 0x00007fffe102034e: mov %r10,0x1f0(%r15) 0x00007fffe1020355: movabs $0x0,%r10 0x00007fffe102035f: mov %r10,0x200(%r15) 0x00007fffe1020366: cmpq $0x0,0x8(%r15) 0x00007fffe102036e: je 0x00007fffe1020379 0x00007fffe1020374: jmpq 0x00007fffe1000420 0x00007fffe1020379: mov -0x38(%rbp),%r13 0x00007fffe102037d: mov -0x30(%rbp),%r14 0x00007fffe1020381: retq
如上代码完成的事情很简单,就是调用C++函数编写的InterpreterRuntime::resolve_get_put()函数,此函数会填充常量池缓存中ConstantPoolCacheEntry信息,关于ConstantPoolCache以及ConstantPoolCacheEntry,还有ConstantPoolCacheEntry中各个字段的含义在《深入剖析Java虚拟机:源码剖析与实例详解(基础卷)》中已经详细介绍过,这里不再介绍。
0x00007fffe1020382: movzwl 0x1(%r13),%edx 0x00007fffe1020387: mov -0x28(%rbp),%rcx 0x00007fffe102038b: 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 segv 0x00007fffe1020398: cmp (%rcx),%rax // 将_flags向右移动28位,剩下TosState 0x00007fffe102039b: shr $0x1c,%eax 0x00007fffe102039e: and $0xf,%eax // 如果不相等,说明TosState的值不为0,则跳转到notByte 0x00007fffe10203a1: jne 0x00007fffe10203ba // btos // btos的编号为0,代码执行到这里时,可能栈顶缓存要求是btos // %rcx中存储的是objectref,%rbx中存储的是_f2,获取字段对应的值存储到%rax中 0x00007fffe10203a7: movsbl (%rcx,%rbx,1),%eax 0x00007fffe10203ab: 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,%eax 0x00007fffe10203bd: jne 0x00007fffe102045d // 跳转到notObj // atos // 调用MacroAssembler::load_heap_oop()函数生成如下代码 0x00007fffe10203c3: mov (%rcx,%rbx,1),%eax // ... 省略部分代码 // 结束MacroAssembler::load_heap_oop()函数的调用 0x00007fffe102044e: push %rax // 重写字节码指令为Bytecodes::_fast_agetfield 0x00007fffe102044f: mov $0xcb,%ecx 0x00007fffe1020454: mov %cl,0x0(%r13) 0x00007fffe1020458: jmpq 0x00007fffe102050f // -- notObj -- 0x00007fffe102045d: cmp $0x3,%eax 0x00007fffe1020460: jne 0x00007fffe1020478 // 跳转到notInt // itos 0x00007fffe1020466: mov (%rcx,%rbx,1),%eax 0x00007fffe1020469: push %rax // 重写字节码指令o Bytecodes::_fast_igetfield 0x00007fffe102046a: mov $0xd0,%ecx 0x00007fffe102046f: mov %cl,0x0(%r13) 0x00007fffe1020473: jmpq 0x00007fffe102050f // --- notInt ---- 0x00007fffe1020478: cmp $0x1,%eax 0x00007fffe102047b: jne 0x00007fffe1020494 // 跳转到notChar // ctos 0x00007fffe1020481: movzwl (%rcx,%rbx,1),%eax 0x00007fffe1020485: push %rax // 重写字节码指令为Bytecodes::_fast_cgetfield 0x00007fffe1020486: mov $0xcd,%ecx 0x00007fffe102048b: mov %cl,0x0(%r13) 0x00007fffe102048f: jmpq 0x00007fffe102050f // ---- notChar ---- 0x00007fffe1020494: cmp $0x2,%eax 0x00007fffe1020497: jne 0x00007fffe10204b0 // 跳转到notShort // stos 0x00007fffe102049d: movswl (%rcx,%rbx,1),%eax 0x00007fffe10204a1: push %rax // 重写字节码指令为Bytecodes::_fast_sgetfield 0x00007fffe10204a2: mov $0xd2,%ecx 0x00007fffe10204a7: mov %cl,0x0(%r13) 0x00007fffe10204ab: jmpq 0x00007fffe102050f // ---- notShort ---- 0x00007fffe10204b0: cmp $0x4,%eax 0x00007fffe10204b3: jne 0x00007fffe10204d3 // 跳转到notLong // ltos 0x00007fffe10204b9: mov (%rcx,%rbx,1),%rax 0x00007fffe10204bd: sub $0x10,%rsp 0x00007fffe10204c1: mov %rax,(%rsp) // 重写字节码指令为Bytecodes::_fast_lgetfield, 0x00007fffe10204c5: mov $0xd1,%ecx 0x00007fffe10204ca: mov %cl,0x0(%r13) 0x00007fffe10204ce: jmpq 0x00007fffe102050f // ---- notLong ---- 0x00007fffe10204d3: cmp $0x5,%eax 0x00007fffe10204d6: jne 0x00007fffe10204f8 // 跳转到notFloat // ftos 0x00007fffe10204dc: vmovss (%rcx,%rbx,1),%xmm0 0x00007fffe10204e1: sub $0x8,%rsp 0x00007fffe10204e5: vmovss %xmm0,(%rsp) // 重写字节码指令为Bytecodes::_fast_fgetfield 0x00007fffe10204ea: mov $0xcf,%ecx 0x00007fffe10204ef: mov %cl,0x0(%r13) 0x00007fffe10204f3: jmpq 0x00007fffe102050f // ---- notFloat ---- 0x00007fffe10204f8: vmovsd (%rcx,%rbx,1),%xmm0 0x00007fffe10204fd: sub $0x10,%rsp 0x00007fffe1020501: vmovsd %xmm0,(%rsp) 0x00007fffe1020506: mov $0xce,%ecx 0x00007fffe102050b: 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),%ebx 0x00007fffe101e4e6: mov -0x28(%rbp),%rcx 0x00007fffe101e4ea: 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后就 // 能获取到偏移的值,然后加载到%eax 0x00007fffe101e4f5: 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),%edx 0x00007fffe101ff95: mov -0x28(%rbp),%rcx 0x00007fffe101ff99: shl $0x2,%edx 0x00007fffe101ff9c: mov 0x10(%rcx,%rdx,8),%ebx 0x00007fffe101ffa0: shr $0x18,%ebx 0x00007fffe101ffa3: and $0xff,%ebx // 是否已经对putstatic指令进行了连接,如果已经连接,则跳转到resolved 0x00007fffe101ffa9: cmp $0xb3,%ebx 0x00007fffe101ffaf: je 0x00007fffe102004e
调用TemplateTable::resolve_cache_and_index()函数生成如下汇编代码:
// 执行到这里,说明字段还没有连接 0x00007fffe101ffb5: mov $0xb3,%ebx // 调用MacroAssembler::call_VM()函数生成如下代码, // 用来执行InterpreterRuntime::resolve_get_put()函数 0x00007fffe101ffba: callq 0x00007fffe101ffc4 0x00007fffe101ffbf: jmpq 0x00007fffe1020042 0x00007fffe101ffc4: mov %rbx,%rsi 0x00007fffe101ffc7: lea 0x8(%rsp),%rax 0x00007fffe101ffcc: mov %r13,-0x38(%rbp) 0x00007fffe101ffd0: mov %r15,%rdi 0x00007fffe101ffd3: mov %rbp,0x200(%r15) 0x00007fffe101ffda: mov %rax,0x1f0(%r15) 0x00007fffe101ffe1: test $0xf,%esp 0x00007fffe101ffe7: je 0x00007fffe101ffff 0x00007fffe101ffed: sub $0x8,%rsp 0x00007fffe101fff1: callq 0x00007ffff66b567c 0x00007fffe101fff6: add $0x8,%rsp 0x00007fffe101fffa: jmpq 0x00007fffe1020004 0x00007fffe101ffff: callq 0x00007ffff66b567c 0x00007fffe1020004: movabs $0x0,%r10 0x00007fffe102000e: mov %r10,0x1f0(%r15) 0x00007fffe1020015: movabs $0x0,%r10 0x00007fffe102001f: mov %r10,0x200(%r15) 0x00007fffe1020026: cmpq $0x0,0x8(%r15) 0x00007fffe102002e: je 0x00007fffe1020039 0x00007fffe1020034: jmpq 0x00007fffe1000420 0x00007fffe1020039: mov -0x38(%rbp),%r13 0x00007fffe102003d: mov -0x30(%rbp),%r14 0x00007fffe1020041: retq 0x00007fffe1020042: movzwl 0x1(%r13),%edx 0x00007fffe1020047: mov -0x28(%rbp),%rcx 0x00007fffe102004b: shl $0x2,%edx
接下来生成的汇编代码如下:
// ---- resolved ---- // 执行如下代码时,表示字段已经连接完成 0x00007fffe102004e: mov 0x20(%rcx,%rdx,8),%rbx 0x00007fffe1020053: mov 0x28(%rcx,%rdx,8),%eax 0x00007fffe1020057: mov 0x18(%rcx,%rdx,8),%rcx 0x00007fffe102005c: mov 0x70(%rcx),%rcx 0x00007fffe1020060: mov %eax,%edx // 将_flags向右移动21位,判断是否有volatile关键字 0x00007fffe1020062: shr $0x15,%edx 0x00007fffe1020065: and $0x1,%edx // 将_flags向右移动28位,剩下TosState 0x00007fffe1020068: shr $0x1c,%eax // 如果不为btos,则跳转到notByte 0x00007fffe102006b: and $0xf,%eax 0x00007fffe102006e: jne 0x00007fffe1020083 // btos // 将栈顶的值存储到%eax中,这个值会写入到对应的字段中 0x00007fffe1020074: mov (%rsp),%eax 0x00007fffe1020077: add $0x8,%rsp // %rcx为_java_mirror,%rbx为_f2,表示域在类中的偏移 0x00007fffe102007b: mov %al,(%rcx,%rbx,1) 0x00007fffe102007e: jmpq 0x00007fffe10201be // 跳转到Done // -- notByte -- // 如果不为atos,则跳转到notObj 0x00007fffe1020083: cmp $0x7,%eax 0x00007fffe1020086: 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,那么跳转到notInt 0x00007fffe1020130: cmp $0x3,%eax 0x00007fffe1020133: jne 0x00007fffe1020148 // itos 0x00007fffe1020139: mov (%rsp),%eax // 如果不为ctos,则跳转到notChar 0x00007fffe102013c: add $0x8,%rsp 0x00007fffe1020140: mov %eax,(%rcx,%rbx,1) 0x00007fffe1020143: jmpq 0x00007fffe10201be // 跳转到Done 0x00007fffe1020148: cmp $0x1,%eax 0x00007fffe102014b: jne 0x00007fffe1020161 // ctos 0x00007fffe1020151: mov (%rsp),%eax 0x00007fffe1020154: add $0x8,%rsp 0x00007fffe1020158: mov %ax,(%rcx,%rbx,1) 0x00007fffe102015c: jmpq 0x00007fffe10201be // 跳转到Done 0x00007fffe1020161: cmp $0x2,%eax 0x00007fffe1020164: jne 0x00007fffe102017a // stos 0x00007fffe102016a: mov (%rsp),%eax 0x00007fffe102016d: add $0x8,%rsp 0x00007fffe1020171: mov %ax,(%rcx,%rbx,1) 0x00007fffe1020175: jmpq 0x00007fffe10201be // 跳转到Done 0x00007fffe102017a: cmp $0x4,%eax 0x00007fffe102017d: jne 0x00007fffe1020194 // ltos 0x00007fffe1020183: mov (%rsp),%rax 0x00007fffe1020187: add $0x10,%rsp 0x00007fffe102018b: mov %rax,(%rcx,%rbx,1) 0x00007fffe102018f: jmpq 0x00007fffe10201be // 跳转到Done 0x00007fffe1020194: cmp $0x5,%eax 0x00007fffe1020197: jne 0x00007fffe10201b0 // ftos 0x00007fffe102019d: vmovss (%rsp),%xmm0 0x00007fffe10201a2: add $0x8,%rsp 0x00007fffe10201a6: vmovss %xmm0,(%rcx,%rbx,1) 0x00007fffe10201ab: jmpq 0x00007fffe10201be // 跳转到Done // dtos 0x00007fffe10201b0: vmovsd (%rsp),%xmm0 0x00007fffe10201b5: add $0x10,%rsp 0x00007fffe10201b9: vmovsd %xmm0,(%rcx,%rbx,1) // ---- Done ---- 0x00007fffe10201be: test %edx,%edx 0x00007fffe10201c0: je 0x00007fffe10201cb 0x00007fffe10201c6: lock addl $0x0,(%rsp) // ---- notVolatile ----
在如上代码中,最值得关注的2个点如下:
(1)更新引用字段时,通过屏障将对应的卡表项标记为脏,这样可在GC过程中扫描脏卡就可将活跃对象标记出来而不会造成遗漏;
(2)当字段有volatile关键字修饰时,需要填写lock指令前缀,这个前缀在之前介绍x86-64机器指令时没有介绍过,这里摘抄一下别人对此指令的介绍:
Intel手册对 lock 前缀的说明如下:
- 确保被修饰指令执行的原子性;
- 禁止该指令与前面和后面的读写指令重排序;
- 指令执行完后把写缓冲区的所有数据刷新到内存中(这样这个指令之前的其他修改对所有处理器可见)。
在所有的 X86 CPU 上都具有锁定一个特定内存地址的能力,当这个特定内存地址被锁定后,它就可以阻止其他的系统总线读取或修改这个内存地址。这种能力是通过 lock 指令前缀再加上下面的汇编指令来实现的。当使用 lock 指令前缀时,它会使 CPU 宣告一个 lock# 信号,这样就能确保在多处理器系统或多线程竞争的环境下互斥地使用这个内存地址。当指令执行完毕,这个锁定动作也就会消失。
第27篇-虚拟机字节码指令之操作数栈管理指令
操作数栈管理相关的字节码指令如下表所示。
0x57 | pop | 将栈顶数值弹出 (数值不能是long或double类型的) |
0x58 | pop2 | 将栈顶的一个(long或double类型的)或两个数值弹出(其它) |
0x59 | dup | 复制栈顶数值并将复制值压入栈顶 |
0x5a | dup_x1 | 复制栈顶数值并将两个复制值压入栈顶 |
0x5b | dup_x2 | 复制栈顶数值并将三个(或两个)复制值压入栈顶 |
0x5c | dup2 | 复制栈顶一个(long或double类型的)或两个(其它)数值并将复制值压入栈顶 |
0x5d | dup2_x1 | dup_x1 指令的双倍版本 |
0x5e | dup2_x2 | dup_x2 指令的双倍版本 |
0x5f | swap | 将栈最顶端的两个数值互换(数值不能是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),%rax push %rax
swap指令将栈最顶端的两个数值互换(数值不能是long或double类型的)。对应的汇编代码如下:
mov 0x8(%rsp),%rcx mov (%rsp),%rax mov %rcx,(%rsp) mov %rax,0x8(%rsp)
指令的执行逻辑比较简单,这里不再过多介绍。
第28篇-虚拟机字节码指令之控制转移指令
控制转移相关的字节码指令如下表所示。
0x99 | ifeq | 当栈顶int型数值等于0时跳转 |
0x9a | ifne | 当栈顶int型数值不等于0时跳转 |
0x9b | iflt | 当栈顶int型数值小于0时跳转 |
0x9c | ifge | 当栈顶int型数值大于等于0时跳转 |
0x9d | ifgt | 当栈顶int型数值大于0时跳转 |
0x9e | ifle | 当栈顶int型数值小于等于0时跳转 |
0x9f | if_icmpeq | 比较栈顶两int型数值大小,当结果等于0时跳转 |
0xa0 | if_icmpne | 比较栈顶两int型数值大小,当结果不等于0时跳转 |
0xa1 | if_icmplt | 比较栈顶两int型数值大小,当结果小于0时跳转 |
0xa2 | if_icmpge | 比较栈顶两int型数值大小,当结果大于等于0时跳转 |
0xa3 | if_icmpgt | 比较栈顶两int型数值大小,当结果大于0时跳转 |
0xa4 | if_icmple | 比较栈顶两int型数值大小,当结果小于等于0时跳转 |
0xa5 | if_acmpeq | 比较栈顶两引用型数值,当结果相等时跳转 |
0xa6 | if_acmpne | 比较栈顶两引用型数值,当结果不相等时跳转 |
0xa7 | goto | 无条件跳转 |
0xa8 | jsr | 跳转至指定16位offset位置,并将jsr下一条指令地址压入栈顶 |
0xa9 | ret | 返回至本地变量指令的index的指令位置(一般与jsr或jsr_w联合使用) |
0xaa | tableswitch | 用于switch条件跳转,case值连续(可变长度指令) |
0xab | lookupswitch | 用于switch条件跳转,case值不连续(可变长度指令) |
0xac | ireturn | 从当前方法返回int |
0xad | lreturn | 从当前方法返回long |
0xae | freturn | 从当前方法返回float |
0xaf | dreturn | 从当前方法返回double |
0xb0 | areturn | 从当前方法返回对象引用 |
0xb1 | return | 从当前方法返回void |
0xc6 | ifnull | 为null时跳转 |
0xc7 | ifnonnull | 不为null时跳转 |
0xc8 | goto_w | 无条件跳转(宽索引) |
0xc9 | jsr_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),%edx 0x00007fffe1019df9: 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,%r10 0x00007fffe1019e13: 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_taken 0x00007fffe10196c9: 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,%r10 0x00007fffe10196f2: 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),%rbx 0x00007fffe1019fab: and $0xfffffffffffffffc,%rbx // load lo & hi 0x00007fffe1019faf: mov 0x4(%rbx),%ecx 0x00007fffe1019fb2: mov 0x8(%rbx),%edx 0x00007fffe1019fb5: bswap %ecx 0x00007fffe1019fb7: bswap %edx // check against lo & hi // %ecx中存储的是lowbyte 0x00007fffe1019fb9: cmp %ecx,%eax // 如果比低位值还低,则跳转到default_case 0x00007fffe1019fbb: jl 0x00007fffe1019feb // %edx中存储的是highbyte 0x00007fffe1019fc1: cmp %edx,%eax // 如果比高位值还高,则跳转到default_case 0x00007fffe1019fc3: jg 0x00007fffe1019feb // lookup dispatch offset 0x00007fffe1019fc9: sub %ecx,%eax // %rbx中存储的是对齐后的字节码指令地址,%rax中存储的是栈顶缓存值 0x00007fffe1019fcb: mov 0xc(%rbx,%rax,4),%edx // -- continue_execution -- // continue execution 0x00007fffe1019fcf: bswap %edx 0x00007fffe1019fd1: movslq %edx,%rdx 0x00007fffe1019fd4: movzbl 0x0(%r13,%rdx,1),%ebx 0x00007fffe1019fda: add %rdx,%r13 0x00007fffe1019fdd: movabs $0x7ffff73ba4a0,%r10 0x00007fffe1019fe7: jmpq *(%r10,%rbx,8) // -- default_case -- // handle default 0x00007fffe1019feb: mov (%rbx),%edx // 跳转到continue_execution 0x00007fffe1019fed: jmp 0x00007fffe1019fcf