背景
之前被stackframemap属性折腾了一段时间,后来好好学习了一下class文件中的属性,解决了一些问题.最近又被tableswitch和lookupswitch指令弄的莫名其妙的崩溃, 专门记录一下java虚拟机的所有指令, 并做简单的分类, 便于以后检索.
分析java虚拟机指令的意义
越来越多的语言选择在java虚拟机上实现,比喻Scala,Clojure, groovy, Jython, JRuby等等. 利用jvm跨平台特性,java丰富的开源框架和开发环境, 这些语言起点更高,更容易成功. 学习java虚拟机的指令,其意义不言而喻.
指令格式
java虚拟机指令格式是操作码 + 操作数.
除了tableswitch 和 lookupswitch两条指令外, 其余的指令都是定长的.
其中操作码是定长1字节, 所以java虚拟机的指令个数上限是256条,其实真正的指令并没有这么多.
大部分指令没有操作数, 少数指令有一个操作数, 这个操作数占固定的字节数. tableswitch和lookupswitch两条指令操作数的个数是可变的.
指令分类
按操作数个数分
因为java虚拟机是基于操作数栈的,大部分指令都是从操作数栈中取操作数, 操作码后面没有操作数.
0 个操作数:
这类指令最多
0x00 (nop) ~ 0x0f (dconst_1)
0x1a (iload_0) ~ 0x35 (saload)
0x3b (istore_0) ~ 0x83 (lxor)
0x85 (i2l) ~ 0x98 (dcmpg)
0xac (ireturn) ~ 0xb1 (return)
0xbe (arraylength)
0xbf (athrow)
0xc2 (monitorenter)
0xc3 (monitorexit)
1个操作数:
0x10 (bipush) ~ 0x19 (aload)
0x36 (istore) ~ 0x3a (astore)
0xa9 (ret)
0xb2 (getstatic) ~0xbb (new)
不定长操作数
0xaa (tableswitch)
0xab (lookupswitch)
0xc4 (wide) 指令不能单独使用, 必须配合其他几个指令一起使用, wide指令并不算变长指令, 我把它放在这里是因为一些操作码前有该指令, 会扩展这些操作码后的操作数的字节个数.
按功能分
算术运算指令
加法 *add
减法 *sub
乘法 *mul
除法 *div
求余 *rem
取负 *neg
注:
星号 ( *)表示忽略前面的类型)
从局部变量表取数据加载到操作数栈
*load
从常量池中取数据加载到操作数栈
ldc
ldc_w
ldc2_w
加载常量到操作数栈
*const
sipush
bipush
交换栈顶2个操作数
swap
复制栈顶元素
0x59 (dup) ~ 0x5e (dup2_x2)
从操作数栈中取数据
*store
移位指令
左移 ishl lshl
右移 ishr lshr iushr lushr
逻辑运算指令
与 iand land
或 ior lor
异或 ixor lxor
局部变量自增指令
(0x84) iinc
类型转换指令
基本类型的数据转换 0x85 (i2l) ~ 0x93 (i2s)
引用类型检查 0xc0 (checkcast) ~ 0xc1 (instanceof)
比较大小指令
0x94 (lcmp) ~ 0x98 (dcmpg)
条件分支跳转指令
0x99 (ifeq) ~ 0xa6 (if_acmpne)
0xa7 (goto), 0xc8 (goto_w)
0xa8 (jsr), 0xc9 (jsr_w) 与 0xa9 (ret) 一同实现 finally 语句块
0xc6 (ifnull), 0xc7 (ifnonnull)
返回指令
0xac (ireturn) ~ 0xb1 (return)
分配对象
0xbb (new)
0xbc (newarray)
0xc5 (multianewarray)
操作类变量和调用类方法
0xb2 (getstatic)
0xb3 (putstatic)
0xb8 (invokestatic)
操作实例变量和调用实例方法
0xb4 (getfield)
0xb5 (putfield)
0xb6 (invokevirtual) // 调用实例方法
0xb7 (invokespecial) // 调用父类的构造方法
0xb9 (invokeinterface) // 调用接口方法
0xba (invokedynamic) // java se 7 中新增
抛异常指令
0xbf (athrow)
扩展局部变量表索引
0xc4 (wide)
保留指令
0xca (breakpoint) //用于断点调试
0xfe (impdep1)
0xff (impdep2) 这两条指令用于在特定硬件中使用的语言后门.
按操作数长度分
0个字节
同0个操作数
1个字节
0x10 (bipush)
0x12 (ldc)
0x15 (iload) ~ 0x19 (aload)
0x36 (istore) ~ 0x3a (astore)
0xa9 (ret)
2个字节
0x11 (sipush )
0x13 (ldc_w)
0x14 (ldc2_w)
0xb2 (getstatic) ~ 0xbb (new)
0xbd (anewarray)
0xc0 (checkcast)
0xc1 (instanceof)
0xc6 (ifnull)
0xc7 (ifnonnull)
3个字节
分配多维数组指令
0xc5 (multianewarray)
4个字节
0xc8 (goto_w)
0xc9 (jsr_w)
不定长字节
0xaa (tableswitch)
0xab (lookupswitch)
需要注意的指令
定长指令处理比较容易,对于
tableswitch
lookupswitch
wide
这三条变长指令处理需要注意:
tableswitch 和 lookupswitch 有 0 ~ 3 个字节填充, 保证后面的操作数是4字节内存对齐.
当wide 指令之后的操作码是iload,fload,aload,lload,dload,istore,fstore,astore,lstore,dstore 以及 ret 指令之
一时, 扩展形式与wide指令之后的操作码是 iinc 时, 扩展的形式并不一样.