Java字节码指令+ASM记录
Java字节码+ASM学习
Java字节码
字节码常量表
操作码 | JVM指令 | 描述 | 栈行为 |
---|---|---|---|
NOP |
nop |
无操作 | 无变化 |
ACONST_NULL |
aconst_null |
推送null引用 | → null |
ICONST_M1 |
iconst_m1 |
推送int常量-1 | → -1 |
ICONST_0 |
iconst_0 |
推送int常量0 | → 0 |
ICONST_1 |
iconst_1 |
推送int常量1 | → 1 |
ICONST_2 |
iconst_2 |
推送int常量2 | → 2 |
ICONST_3 |
iconst_3 |
推送int常量3 | → 3 |
ICONST_4 |
iconst_4 |
推送int常量4 | → 4 |
ICONST_5 |
iconst_5 |
推送int常量5 | → 5 |
LCONST_0 |
lconst_0 |
推送long常量0 | → 0L |
LCONST_1 |
lconst_1 |
推送long常量1 | → 1L |
FCONST_0 |
fconst_0 |
推送float常量0.0 | → 0.0f |
FCONST_1 |
fconst_1 |
推送float常量1.0 | → 1.0f |
FCONST_2 |
fconst_2 |
推送float常量2.0 | → 2.0f |
DCONST_0 |
dconst_0 |
推送double常量0.0 | → 0.0 |
DCONST_1 |
dconst_1 |
推送double常量1.0 | → 1.0 |
BIPUSH |
bipush |
推送byte常量 | → byte |
SIPUSH |
sipush |
推送short常量 | → short |
LDC |
ldc |
从常量池加载项 | → item |
LDC_W |
ldc_w |
从常量池加载项(宽索引) | → item |
LDC2_W |
ldc2_w |
从常量池加载long/double | → long/double |
局部变量表
操作码 | JVM指令 | 描述 | 栈行为 |
---|---|---|---|
ILOAD |
iload |
加载int局部变量 | → int |
LLOAD |
lload |
加载long局部变量 | → long |
FLOAD |
fload |
加载float局部变量 | → float |
DLOAD |
dload |
加载double局部变量 | → double |
ALOAD |
aload |
加载引用局部变量 | → ref |
ILOAD_0 |
iload_0 |
加载int局部变量0 | → int |
ILOAD_1 |
iload_1 |
加载int局部变量1 | → int |
ILOAD_2 |
iload_2 |
加载int局部变量2 | → int |
ILOAD_3 |
iload_3 |
加载int局部变量3 | → int |
LLOAD_0 |
lload_0 |
加载long局部变量0 | → long |
LLOAD_1 |
lload_1 |
加载long局部变量1 | → long |
LLOAD_2 |
lload_2 |
加载long局部变量2 | → long |
LLOAD_3 |
lload_3 |
加载long局部变量3 | → long |
FLOAD_0 |
fload_0 |
加载float局部变量0 | → float |
FLOAD_1 |
fload_1 |
加载float局部变量1 | → float |
FLOAD_2 |
fload_2 |
加载float局部变量2 | → float |
FLOAD_3 |
fload_3 |
加载float局部变量3 | → float |
DLOAD_0 |
dload_0 |
加载double局部变量0 | → double |
DLOAD_1 |
dload_1 |
加载double局部变量1 | → double |
DLOAD_2 |
dload_2 |
加载double局部变量2 | → double |
DLOAD_3 |
dload_3 |
加载double局部变量3 | → double |
ALOAD_0 |
aload_0 |
加载引用局部变量0 | → ref |
ALOAD_1 |
aload_1 |
加载引用局部变量1 | → ref |
ALOAD_2 |
aload_2 |
加载引用局部变量2 | → ref |
ALOAD_3 |
aload_3 |
加载引用局部变量3 | → ref |
ISTORE |
istore |
存储int到局部变量 | int → |
LSTORE |
lstore |
存储long到局部变量 | long → |
FSTORE |
fstore |
存储float到局部变量 | float → |
DSTORE |
dstore |
存储double到局部变量 | double → |
ASTORE |
astore |
存储引用到局部变量 | ref → |
ISTORE_0 |
istore_0 |
存储int到局部变量0 | int → |
ISTORE_1 |
istore_1 |
存储int到局部变量1 | int → |
ISTORE_2 |
istore_2 |
存储int到局部变量2 | int → |
ISTORE_3 |
istore_3 |
存储int到局部变量3 | int → |
LSTORE_0 |
lstore_0 |
存储long到局部变量0 | long → |
LSTORE_1 |
lstore_1 |
存储long到局部变量1 | long → |
LSTORE_2 |
lstore_2 |
存储long到局部变量2 | long → |
LSTORE_3 |
lstore_3 |
存储long到局部变量3 | long → |
FSTORE_0 |
fstore_0 |
存储float到局部变量0 | float → |
FSTORE_1 |
fstore_1 |
存储float到局部变量1 | float → |
FSTORE_2 |
fstore_2 |
存储float到局部变量2 | float → |
FSTORE_3 |
fstore_3 |
存储float到局部变量3 | float → |
DSTORE_0 |
dstore_0 |
存储double到局部变量0 | double → |
DSTORE_1 |
dstore_1 |
存储double到局部变量1 | double → |
DSTORE_2 |
dstore_2 |
存储double到局部变量2 | double → |
DSTORE_3 |
dstore_3 |
存储double到局部变量3 | double → |
ASTORE_0 |
astore_0 |
存储引用到局部变量0 | ref → |
ASTORE_1 |
astore_1 |
存储引用到局部变量1 | ref → |
ASTORE_2 |
astore_2 |
存储引用到局部变量2 | ref → |
ASTORE_3 |
astore_3 |
存储引用到局部变量3 | ref → |
数组操作指令
操作码 | JVM指令 | 描述 | 栈行为 |
---|---|---|---|
NEWARRAY |
newarray |
创建基本类型数组 | count → arrayref |
ANEWARRAY |
anewarray |
创建引用类型数组 | count → arrayref |
MULTIANEWARRAY |
multianewarray |
创建多维数组 | count1, count2, ... → arrayref |
ARRAYLENGTH |
arraylength |
获取数组长度 | arrayref → length |
BALOAD |
baload |
加载byte/boolean数组元素 | arrayref, index → value |
CALOAD |
caload |
加载char数组元素 | arrayref, index → value |
SALOAD |
saload |
加载short数组元素 | arrayref, index → value |
IALOAD |
iaload |
加载int数组元素 | arrayref, index → value |
LALOAD |
laload |
加载long数组元素 | arrayref, index → value |
FALOAD |
faload |
加载float数组元素 | arrayref, index → value |
DALOAD |
daload |
加载double数组元素 | arrayref, index → value |
AALOAD |
aaload |
加载引用数组元素 | arrayref, index → ref |
BASTORE |
bastore |
存储byte/boolean到数组 | arrayref, index, value → |
CASTORE |
castore |
存储char到数组 | arrayref, index, value → |
SASTORE |
sastore |
存储short到数组 | arrayref, index, value → |
IASTORE |
iastore |
存储int到数组 | arrayref, index, value → |
LASTORE |
lastore |
存储long到数组 | arrayref, index, value → |
FASTORE |
fastore |
存储float到数组 | arrayref, index, value → |
DASTORE |
dastore |
存储double到数组 | arrayref, index, value → |
AASTORE |
aastore |
存储引用到数组 | arrayref, index, ref → |
栈操作指令
操作码 | JVM指令 | 描述 | 栈行为 |
---|---|---|---|
POP |
pop |
弹出栈顶元素 | value → |
POP2 |
pop2 |
弹出栈顶一个或两个元素 | value1, value2 → |
DUP |
dup |
复制栈顶元素 | value → value, value |
DUP_X1 |
dup_x1 |
复制栈顶元素并插入到栈顶下方 | value1, value2 → value1, value2, value1 |
DUP_X2 |
dup_x2 |
复制栈顶元素并插入到栈顶下方两个位置 | value1, value2, value3 → value1, value2, value3, value1 |
DUP2 |
dup2 |
复制栈顶一个或两个元素 | value1, value2 → value1, value2, value1, value2 |
DUP2_X1 |
dup2_x1 |
复制栈顶两个元素并插入到栈顶下方 | value1, value2, value3 → value1, value2, value3, value1, value2 |
DUP2_X2 |
dup2_x2 |
复制栈顶两个元素并插入到栈顶下方两个位置 | value1, value2, value3, value4 → value1, value2, value3, value4, value1, value2 |
SWAP |
swap |
交换栈顶两个元素 | value1, value2 → value2, value1 |
数学运算指令
操作码 | JVM指令 | 描述 | 栈行为 |
---|---|---|---|
IADD |
iadd |
int加法 | value1, value2 → result |
LADD |
ladd |
long加法 | value1, value2 → result |
FADD |
fadd |
float加法 | value1, value2 → result |
DADD |
dadd |
double加法 | value1, value2 → result |
ISUB |
isub |
int减法 | value1, value2 → result |
LSUB |
lsub |
long减法 | value1, value2 → result |
FSUB |
fsub |
float减法 | value1, value2 → result |
DSUB |
dsub |
double减法 | value1, value2 → result |
IMUL |
imul |
int乘法 | value1, value2 → result |
LMUL |
lmul |
long乘法 | value1, value2 → result |
FMUL |
fmul |
float乘法 | value1, value2 → result |
DMUL |
dmul |
double乘法 | value1, value2 → result |
IDIV |
idiv |
int除法 | value1, value2 → result |
LDIV |
ldiv |
long除法 | value1, value2 → result |
FDIV |
fdiv |
float除法 | value1, value2 → result |
DDIV |
ddiv |
double除法 | value1, value2 → result |
IREM |
irem |
int取余 | value1, value2 → result |
LREM |
lrem |
long取余 | value1, value2 → result |
FREM |
frem |
float取余 | value1, value2 → result |
DREM |
drem |
double取余 | value1, value2 → result |
INEG |
ineg |
int取负 | value → result |
LNEG |
lneg |
long取负 | value → result |
FNEG |
fneg |
float取负 | value → result |
DNEG |
dneg |
double取负 | value → result |
ISHL |
ishl |
int左移 | value1, value2 → result |
LSHL |
lshl |
long左移 | value1, value2 → result |
ISHR |
ishr |
int算术右移 | value1, value2 → result |
LSHR |
lshr |
long算术右移 | value1, value2 → result |
IUSHR |
iushr |
int逻辑右移 | value1, value2 → result |
LUSHR |
lushr |
long逻辑右移 | value1, value2 → result |
IAND |
iand |
int按位与 | value1, value2 → result |
LAND |
land |
long按位与 | value1, value2 → result |
IOR |
ior |
int按位或 | value1, value2 → result |
LOR |
lor |
long按位或 | value1, value2 → result |
IXOR |
ixor |
int按位异或 | value1, value2 → result |
LXOR |
lxor |
long按位异或 | value1, value2 → result |
IINC |
iinc |
局部变量自增 | 无栈变化 |
类型转换指令
操作码 | JVM指令 | 描述 | 栈行为 |
---|---|---|---|
I2L |
i2l |
int转long | int → long |
I2F |
i2f |
int转float | int → float |
I2D |
i2d |
int转double | int → double |
L2I |
l2i |
long转int | long → int |
L2F |
l2f |
long转float | long → float |
L2D |
l2d |
long转double | long → double |
F2I |
f2i |
float转int | float → int |
F2L |
f2l |
float转long | float → long |
F2D |
f2d |
float转double | float → double |
D2I |
d2i |
double转int | double → int |
D2L |
d2l |
double转long | double → long |
D2F |
d2f |
double转float | double → float |
I2B |
i2b |
int转byte | int → byte |
I2C |
i2c |
int转char | int → char |
I2S |
i2s |
int转short | int → short |
CHECKCAST |
checkcast |
类型检查 | ref → ref |
INSTANCEOF |
instanceof |
实例检查 | ref → result |
比较指令
操作码 | JVM指令 | 描述 | 栈行为 |
---|---|---|---|
LCMP |
lcmp |
long比较 | value1, value2 → result |
FCMPL |
fcmpl |
float比较(-1在NaN) | value1, value2 → result |
FCMPG |
fcmpg |
float比较(1在NaN) | value1, value2 → result |
DCMPL |
dcmpl |
double比较(-1在NaN) | value1, value2 → result |
DCMPG |
dcmpg |
double比较(1在NaN) | value1, value2 → result |
IFEQ |
ifeq |
int等于0跳转 | value → |
IFNE |
ifne |
int不等于0跳转 | value → |
IFLT |
iflt |
int小于0跳转 | value → |
IFGE |
ifge |
int大于等于0跳转 | value → |
IFGT |
ifgt |
int大于0跳转 | value → |
IFLE |
ifle |
int小于等于0跳转 | value → |
IF_ICMPEQ |
if_icmpeq |
int相等跳转 | value1, value2 → |
IF_ICMPNE |
if_icmpne |
int不等跳转 | value1, value2 → |
IF_ICMPLT |
if_icmplt |
int小于跳转 | value1, value2 → |
IF_ICMPGE |
if_icmpge |
int大于等于跳转 | value1, value2 → |
IF_ICMPGT |
if_icmpgt |
int大于跳转 | value1, value2 → |
IF_ICMPLE |
if_icmple |
int小于等于跳转 | value1, value2 → |
IF_ACMPEQ |
if_acmpeq |
引用相等跳转 | ref1, ref2 → |
IF_ACMPNE |
if_acmpne |
引用不等跳转 | ref1, ref2 → |
IFNULL |
ifnull |
引用为null跳转 | ref → |
IFNONNULL |
ifnonnull |
引用不为null跳转 | ref → |
控制流指令
操作码 | JVM指令 | 描述 | 栈行为 |
---|---|---|---|
GOTO |
goto |
无条件跳转 | 无栈变化 |
JSR |
jsr |
跳转到子程序 | → address |
RET |
ret |
从子程序返回 | 无栈变化 |
TABLESWITCH |
tableswitch |
表跳转 | index → |
LOOKUPSWITCH |
lookupswitch |
查找跳转 | key → |
IRETURN |
ireturn |
int方法返回 | value → [empty] |
LRETURN |
lreturn |
long方法返回 | value → [empty] |
FRETURN |
freturn |
float方法返回 | value → [empty] |
DRETURN |
dreturn |
double方法返回 | value → [empty] |
ARETURN |
areturn |
引用方法返回 | ref → [empty] |
RETURN |
return |
void方法返回 | → [empty] |
方法调用指令
操作码 | JVM指令 | 描述 | 栈行为 |
---|---|---|---|
INVOKEVIRTUAL |
invokevirtual |
调用实例方法 | objectref, [arg1, arg2, ...] → result |
INVOKESPECIAL |
invokespecial |
调用实例特殊方法(构造/私有/父类) | objectref, [arg1, arg2, ...] → result |
INVOKESTATIC |
invokestatic |
调用静态方法 | [arg1, arg2, ...] → result |
INVOKEINTERFACE |
invokeinterface |
调用接口方法 | objectref, [arg1, arg2, ...] → result |
INVOKEDYNAMIC |
invokedynamic |
调用动态方法 | [arg1, arg2, ...] → result |
字段访问指令
操作码 | JVM指令 | 描述 | 栈行为 |
---|---|---|---|
GETSTATIC |
getstatic |
获取静态字段 | → value |
PUTSTATIC |
putstatic |
设置静态字段 | value → |
GETFIELD |
getfield |
获取实例字段 | objectref → value |
PUTFIELD |
putfield |
设置实例字段 | objectref, value → |
对象操作指令
操作码 | JVM指令 | 描述 | 栈行为 |
---|---|---|---|
NEW |
new |
创建新对象 | → objectref |
NEWARRAY |
newarray |
创建基本类型数组 | count → arrayref |
ANEWARRAY |
anewarray |
创建引用类型数组 | count → arrayref |
MULTIANEWARRAY |
multianewarray |
创建多维数组 | count1, count2, ... → arrayref |
ARRAYLENGTH |
arraylength |
获取数组长度 | arrayref → length |
INSTANCEOF |
instanceof |
实例检查 | ref → result |
CHECKCAST |
checkcast |
类型检查 | ref → ref |
异常处理指令
操作码 | JVM指令 | 描述 | 栈行为 |
---|---|---|---|
ATHROW |
athrow |
抛出异常 | objectref → [empty] |
同步指令
操作码 | JVM指令 | 描述 | 栈行为 |
---|---|---|---|
MONITORENTER |
monitorenter |
获取对象锁 | objectref → |
MONITOREXIT |
monitorexit |
释放对象锁 | objectref → |
其他指令
操作码 | JVM指令 | 描述 | 栈行为 |
---|---|---|---|
WIDE |
wide |
扩展局部变量索引 | 无栈变化 |
BREAKPOINT |
breakpoint |
调试断点 | 无栈变化 |
IMPDEP1 |
impdep1 |
保留指令 | 无栈变化 |
IMPDEP2 |
impdep2 |
保留指令 | 无栈变化 |
示例分析
警告: 二进制文件TestAsm包含org.n1es.TestAsm
Compiled from "TestAsm.java"
public class org.n1es.TestAsm {
public org.n1es.TestAsm();
Code:
0: aload_0 // 加载This对象 aload指令为加载引用局部变量表下标为0的引用对象到操作数栈上
1: invokespecial #1 // Method java/lang/Object."<init>":()V // invokespecial 调用实例特殊方法(构造/私有/父类)
4: return // return指令为void,无返回,areturn为引用对象返回,其他return指令表中都有列举。
public java.lang.Object test(java.lang.String, java.lang.Object, int, long);
Code:
0: iconst_4 // 推送4个int常量
1: anewarray #2 // class java/lang/Object 创建一个Object类型的引用数组,数组固定4个index
4: dup // 复制栈顶元素 anewarray到操作数栈
5: iconst_0 // 指向第0个位置
6: aload_1 // 加载引用变量1,test方法的第一个入参
7: aastore // AASTORE指令会存储刚load的参数到数组
8: dup // 继续复制anewarray到栈顶
9: iconst_1 // 对索引位置1进行操作,如上
10: aload_2
11: aastore
12: dup
13: iconst_2 // 索引2操作
14: iload_3
15: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; // 执行静态方法valueOf(index3)
18: aastore // 然后存储到数组
19: dup
20: iconst_3
21: lload 4
23: invokestatic #4 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long; // 同上处理
26: aastore
27: astore 6 // 存储数组引用对象到局部变量表中
29: aconst_null // 指向一个null指令
30: areturn // 返回上面的null引用
上述方法:
public Object test(String var1,Object var2,int var3,long var4){
Object[] var5 = new Object[]{var1,var2,var3,var4};
return null;
}
翻译过来还是很简单的,但直接看字节码指令还是有点晦涩和繁琐,需要对Java栈操作有一定的理解,并熟悉各个指令的作用。
Class文件格式
Class文件是一个二进制文件,严格按照特定格式组织:
如果在对字节码进行解析研究时,方便理解Class字节格式。
ClassFile {
u4 magic; // 魔数 0xCAFEBABE
u2 minor_version; // 次版本号
u2 major_version; // 主版本号
u2 constant_pool_count; // 常量池计数
cp_info constant_pool[constant_pool_count-1]; // 常量池
u2 access_flags; // 访问标志
u2 this_class; // 当前类索引
u2 super_class; // 父类索引
u2 interfaces_count; // 接口计数
u2 interfaces[interfaces_count]; // 接口索引表
u2 fields_count; // 字段计数
field_info fields[fields_count]; // 字段表
u2 methods_count; // 方法计数
method_info methods[methods_count]; // 方法表
u2 attributes_count; // 属性计数
attribute_info attributes[attributes_count]; // 属性表
}
JVM运行时数据区
内存区域划分
┌─────────────────────────────────────────────────────────┐
│ JVM内存结构 │
├─────────────────────────────────────────────────────────┤
│ 方法区(Method Area) │
│ ├─ 运行时常量池(Runtime Constant Pool) │
│ ├─ 类信息(Class Information) │
│ └─ 方法信息(Method Information) │
├─────────────────────────────────────────────────────────┤
│ 堆(Heap) │
│ ├─ 新生代(Young Generation) │
│ └─ 老年代(Old Generation) │
├─────────────────────────────────────────────────────────┤
│ JVM栈(JVM Stack) - 每个线程一个 │
│ ├─ 栈帧(Stack Frame) │
│ │ ├─ 局部变量表(Local Variable Table) │
│ │ ├─ 操作数栈(Operand Stack) │
│ │ ├─ 动态链接(Dynamic Linking) │
│ │ └─ 方法返回地址(Return Address) │
├─────────────────────────────────────────────────────────┤
│ 程序计数器(PC Register) - 每个线程一个 │
├─────────────────────────────────────────────────────────┤
│ 本地方法栈(Native Method Stack) - 每个线程一个 │
└─────────────────────────────────────────────────────────┘
栈帧结构详解
每个方法调用都会创建一个栈帧:
public class StackFrameExample {
public int calculate(int a, int b) {
int result = a + b;
int doubled = result * 2;
return doubled;
}
}
对应的栈针结构:
┌─────────────────────────────────────┐
│ 栈帧(Stack Frame) │
├─────────────────────────────────────┤
│ 局部变量表(Local Variable Table) │
│ ├─ 0: this │
│ ├─ 1: a (参数) │
│ ├─ 2: b (参数) │
│ ├─ 3: result (局部变量) │
│ └─ 4: doubled (局部变量) │
├─────────────────────────────────────┤
│ 操作数栈(Operand Stack) │
│ ├─ [栈顶] │
│ ├─ ... │
│ └─ [栈底] │
├─────────────────────────────────────┤
│ 动态链接 │
├─────────────────────────────────────┤
│ 方法返回地址 │
└─────────────────────────────────────┘
栈操作指令:
NOP // 无操作
POP // 弹出栈顶1个字长
POP2 // 弹出栈顶2个字长
DUP // 复制栈顶1个字长
DUP_X1 // 复制栈顶1个字长,插入到栈顶2个字长下
DUP_X2 // 复制栈顶1个字长,插入到栈顶3个字长下
DUP2 // 复制栈顶2个字长
DUP2_X1 // 复制栈顶2个字长,插入到栈顶3个字长下
DUP2_X2 // 复制栈顶2个字长,插入到栈顶4个字长下
SWAP // 交换栈顶两个字长
ASM字节码
类结构概述
内部名
在已编译类中需要用内部名进行表示,一个类的内部名就是这个类的完全限定名,其中的.
用/
代替。例如:String的内部名为java/lang/String
。
类型描述符
内部名只能用于类或接口类型。所有其他 Java 类型,比如字段类型,在已编译类中都是用
类型描述符表示的
Java 类型 | 类型描述符 |
---|---|
boolean | Z |
char | C |
byte | B |
short | S |
int | I |
float | F |
Long | J |
double | D |
Object | Ljava/lang/Object; |
int[] | [I |
Object[][] |
[[Ljava/lang/Object; |
基元类型的描述符是单个字符:Z 表示 boolean,C 表示 char,B 表示 byte,S 表示
short, I 表示 int,F 表示 float,J 表示 long,D 表示 double。一个类类型的描述符
是这个类的 内部名, 前面加上字符 L , 后面跟有一个分号。例如, String 的类型描述符为
Ljava/lang/String;。而一个数组类型的描述符是一个方括号后面跟有该数组元素类型的描
述符。
方法描述符
方法描述符是一个类型描述符列表,它用一个字符串描述一个方法的参数类型和返回类型。 方
法描述符以左括号开头,然后是每个形参的类型描述符,然后是一个右括号,接下来是返回类 型的
类型描述符,如果该方法返回 void,则是 V(方法描述符中不包含方法的名字或参数名)。
源文件中的方法声明 | 方法描述符 |
---|---|
void m(int i, float f) | (IF)V |
int m(Object o) | (Ljava/lang/Object;)I |
int[] m(int i, String s) | (ILjava/lang/String;)[I |
Type
一个Type对象表示一种Java类型,既可以由类型描述符构造,也可以由Class对象构建。Type类还包含表示基元类型的静态变量。例如:Type.INT_TYPE是表示int类型的Type对象。
getInternalName方法返回一个Type的内部名。例如,Type.getType(String.class).getInternalName()给出String类的内部名,即“Java/lang/String”
getDescriptor方法返回一个Type的描述符,比如,在代码中可以不适用“Ljava/lang/String;”,直接Type.getType(String.class).getDescriptor()获取即可。
getArgumentTypes获取一个方法的参数类型
getReturnType获取方法的返回类型。
TraceClassVisitor
要确认所生成或转换后的类符合你的预期,ClassWriter 返回的字母数组并没有什么真正
的用处,因为它对人类来说是不可读的。如果有文本表示形式,那使用起来就容易多了。这正是
TraceClassVisitor 类提供的东西。从名字可以看出,这个类扩展了 ClassVisitor
类, 并生成所访问类的文本表示。TraceClassVisitor 可以将对其方
法 的所有调用委托给另一个访问器,也可以把classVisitor的调用委托给TraceClassVisitor,比如在实现RASP时,我把ClassVisitor委托给了TraceClassVisitor,对修改后的指定方法进行打印:
public class RASPTraceClassVisitor {
private final ClassReader cr;
private final CopyOnWriteArrayList<RASPConfig> raspConfigs;
private final String className;
public RASPTraceClassVisitor(String className, CopyOnWriteArrayList<RASPConfig> raspConfigs, byte[] classfileBuffer) {
this.className = className;
this.raspConfigs = raspConfigs;
this.cr = new ClassReader(classfileBuffer);
}
public void traceVisitor() {
TraceClassVisitor traceClassVisitor = new TraceClassVisitor(new PrintWriter(System.out));
cr.accept(new ClassVisitor(ASM9, traceClassVisitor) {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
for (RASPConfig raspConfig : raspConfigs) {
if (raspConfig.getOriginClassName().equals(className)) {
if (raspConfig.getMethodName().equals(name)) {
if (raspConfig.getMethodDescriptor().equals(descriptor))
return traceClassVisitor.visitMethod(access, name, descriptor, signature, exceptions);
}
}
}
return null;
}
}, EXPAND_FRAMES);
}
}
打印结果:
// class version 52.0 (52)
// access flags 0x21
public class java/lang/Runtime {
// compiled from: Runtime.java
// access flags 0xA
private static Ljava/lang/Runtime; currentRuntime
// access flags 0x1
public exec(Ljava/lang/String;)Ljava/lang/Process; throws java/io/IOException
TRYCATCHBLOCK L0 L1 L2 java/lang/RuntimeException
L0
ICONST_1
ANEWARRAY java/lang/Object
DUP
ICONST_0
ALOAD 1
AASTORE
ASTORE 2
ALOAD 0
ALOAD 2
ACONST_NULL
LDC "java.lang.Runtime"
LDC "exec"
LDC "(Ljava/lang/String;)Ljava/lang/Process;"
LDC 2101440631
ICONST_1
LDC "org.n1es.rasp.agent.hook.interceptor.RuntimeExecInterceptor"
INVOKESTATIC org/n1es/processor/BootstrapProcessor.bootstrapProcess (Ljava/lang/Object;[Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILjava/lang/String;)Lorg/n1es/common/hook/HookResult;
ASTORE 3
ALOAD 3
INVOKEVIRTUAL org/n1es/common/hook/HookResult.isAllowed ()Z
ISTORE 4
ILOAD 4
IFNE L3
ALOAD 3
INVOKEVIRTUAL org/n1es/common/hook/HookResult.getException ()Ljava/lang/Exception;
ATHROW
L3
LINENUMBER 347 L3
FRAME FULL [java/lang/Runtime java/lang/String [Ljava/lang/Object; org/n1es/common/hook/HookResult I] []
ALOAD 0
ALOAD 1
ACONST_NULL
ACONST_NULL
INVOKEVIRTUAL java/lang/Runtime.exec (Ljava/lang/String;[Ljava/lang/String;Ljava/io/File;)Ljava/lang/Process;
ASTORE 5
ALOAD 0
ALOAD 2
ALOAD 5
LDC "java.lang.Runtime"
LDC "exec"
LDC "(Ljava/lang/String;)Ljava/lang/Process;"
LDC 2101440631
ICONST_2
LDC "org.n1es.rasp.agent.hook.interceptor.RuntimeExecInterceptor"
INVOKESTATIC org/n1es/processor/BootstrapProcessor.bootstrapProcess (Ljava/lang/Object;[Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILjava/lang/String;)Lorg/n1es/common/hook/HookResult;
ASTORE 6
ALOAD 6
INVOKEVIRTUAL org/n1es/common/hook/HookResult.isAllowed ()Z
ISTORE 7
ILOAD 7
IFNE L4
ALOAD 6
INVOKEVIRTUAL org/n1es/common/hook/HookResult.getException ()Ljava/lang/Exception;
ATHROW
L4
FRAME FULL [java/lang/Runtime java/lang/String [Ljava/lang/Object; org/n1es/common/hook/HookResult I java/lang/Process org/n1es/common/hook/HookResult I] []
ALOAD 5
L1
ARETURN
L2
FRAME FULL [java/lang/Runtime java/lang/String] [java/lang/RuntimeException]
DUP
INVOKEVIRTUAL java/lang/RuntimeException.printStackTrace ()V
ATHROW
MAXSTACK = 9
MAXLOCALS = 8
}
在这里我们精准控制了需要printWriter处理的方法,只打印exec方法
ASMifier
这个类在访问一个已经存在的类时,会打印用ASM方式生成这个类的源代码,在你不知道如何用ASM生成某个已编译类,可以通过该类去试试。
ASMifier.main(new String[] { "org.n1es.asm.RuntimeHook" });
结果:
ClassWriter classWriter = new ClassWriter(0);
FieldVisitor fieldVisitor;
RecordComponentVisitor recordComponentVisitor;
MethodVisitor methodVisitor;
AnnotationVisitor annotationVisitor0;
classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "org/n1es/asm/RuntimeHook", null, "java/lang/Object", null);
classWriter.visitSource("RuntimeHook.java", null);
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(9, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
methodVisitor.visitInsn(RETURN);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLocalVariable("this", "Lorg/n1es/asm/RuntimeHook;", null, label0, label1, 0);
methodVisitor.visitMaxs(1, 1);
methodVisitor.visitEnd();
}
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "exec", "(Ljava/lang/String;)Ljava/lang/Process;", null, new String[] { "java/io/IOException" });
methodVisitor.visitCode();
Label label0 = new Label();
Label label1 = new Label();
Label label2 = new Label();
methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/RuntimeException");
Label label3 = new Label();
methodVisitor.visitTryCatchBlock(label0, label1, label3, "java/lang/Exception");
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(13, label0);
methodVisitor.visitInsn(ICONST_1);
methodVisitor.visitTypeInsn(ANEWARRAY, "java/lang/Object");
methodVisitor.visitInsn(DUP);
methodVisitor.visitInsn(ICONST_0);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitInsn(AASTORE);
methodVisitor.visitVarInsn(ASTORE, 2);
Label label4 = new Label();
methodVisitor.visitLabel(label4);
methodVisitor.visitLineNumber(14, label4);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitVarInsn(ALOAD, 2);
methodVisitor.visitInsn(ACONST_NULL);
methodVisitor.visitLdcInsn("java.lang.Runtime");
methodVisitor.visitLdcInsn("exec");
methodVisitor.visitLdcInsn("(Ljava/lang/String;)Ljava/lang/Process;");
methodVisitor.visitLdcInsn(new Integer(2101440631));
methodVisitor.visitInsn(ICONST_1);
methodVisitor.visitLdcInsn("org.n1es.rasp.agent.hook.interceptor.RuntimeExecInterceptor");
methodVisitor.visitMethodInsn(INVOKESTATIC, "org/n1es/processor/BootstrapProcessor", "bootstrapProcess", "(Ljava/lang/Object;[Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILjava/lang/String;)Lorg/n1es/common/hook/HookResult;", false);
methodVisitor.visitVarInsn(ASTORE, 3);
Label label5 = new Label();
methodVisitor.visitLabel(label5);
methodVisitor.visitLineNumber(16, label5);
methodVisitor.visitVarInsn(ALOAD, 3);
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "org/n1es/common/hook/HookResult", "isAllowed", "()Z", false);
methodVisitor.visitVarInsn(ISTORE, 4);
Label label6 = new Label();
methodVisitor.visitLabel(label6);
methodVisitor.visitLineNumber(17, label6);
methodVisitor.visitVarInsn(ILOAD, 4);
Label label7 = new Label();
methodVisitor.visitJumpInsn(IFNE, label7);
Label label8 = new Label();
methodVisitor.visitLabel(label8);
methodVisitor.visitLineNumber(18, label8);
methodVisitor.visitVarInsn(ALOAD, 3);
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "org/n1es/common/hook/HookResult", "getException", "()Ljava/lang/Exception;", false);
methodVisitor.visitInsn(ATHROW);
methodVisitor.visitLabel(label7);
methodVisitor.visitLineNumber(20, label7);
methodVisitor.visitFrame(Opcodes.F_APPEND,3, new Object[] {"[Ljava/lang/Object;", "org/n1es/common/hook/HookResult", Opcodes.INTEGER}, 0, null);
methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Runtime", "getRuntime", "()Ljava/lang/Runtime;", false);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitInsn(ACONST_NULL);
methodVisitor.visitTypeInsn(CHECKCAST, "[Ljava/lang/String;");
methodVisitor.visitInsn(ACONST_NULL);
methodVisitor.visitTypeInsn(CHECKCAST, "java/io/File");
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Runtime", "exec", "(Ljava/lang/String;[Ljava/lang/String;Ljava/io/File;)Ljava/lang/Process;", false);
methodVisitor.visitVarInsn(ASTORE, 5);
Label label9 = new Label();
methodVisitor.visitLabel(label9);
methodVisitor.visitLineNumber(21, label9);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitVarInsn(ALOAD, 2);
methodVisitor.visitVarInsn(ALOAD, 5);
methodVisitor.visitLdcInsn("java.lang.Runtime");
methodVisitor.visitLdcInsn("exec");
methodVisitor.visitLdcInsn("(Ljava/lang/String;)Ljava/lang/Process;");
methodVisitor.visitLdcInsn(new Integer(2101440631));
methodVisitor.visitInsn(ICONST_2);
methodVisitor.visitLdcInsn("org.n1es.rasp.agent.hook.interceptor.RuntimeExecInterceptor");
methodVisitor.visitMethodInsn(INVOKESTATIC, "org/n1es/processor/BootstrapProcessor", "bootstrapProcess", "(Ljava/lang/Object;[Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILjava/lang/String;)Lorg/n1es/common/hook/HookResult;", false);
methodVisitor.visitVarInsn(ASTORE, 6);
Label label10 = new Label();
methodVisitor.visitLabel(label10);
methodVisitor.visitLineNumber(22, label10);
methodVisitor.visitVarInsn(ALOAD, 6);
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "org/n1es/common/hook/HookResult", "isAllowed", "()Z", false);
methodVisitor.visitVarInsn(ISTORE, 7);
Label label11 = new Label();
methodVisitor.visitLabel(label11);
methodVisitor.visitLineNumber(23, label11);
methodVisitor.visitVarInsn(ILOAD, 7);
Label label12 = new Label();
methodVisitor.visitJumpInsn(IFNE, label12);
Label label13 = new Label();
methodVisitor.visitLabel(label13);
methodVisitor.visitLineNumber(24, label13);
methodVisitor.visitVarInsn(ALOAD, 6);
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "org/n1es/common/hook/HookResult", "getException", "()Ljava/lang/Exception;", false);
methodVisitor.visitInsn(ATHROW);
methodVisitor.visitLabel(label12);
methodVisitor.visitLineNumber(26, label12);
methodVisitor.visitFrame(Opcodes.F_APPEND,3, new Object[] {"java/lang/Process", "org/n1es/common/hook/HookResult", Opcodes.INTEGER}, 0, null);
methodVisitor.visitVarInsn(ALOAD, 5);
methodVisitor.visitLabel(label1);
methodVisitor.visitInsn(ARETURN);
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(29, label2);
methodVisitor.visitFrame(Opcodes.F_FULL, 2, new Object[] {"org/n1es/asm/RuntimeHook", "java/lang/String"}, 1, new Object[] {"java/lang/RuntimeException"});
methodVisitor.visitVarInsn(ASTORE, 2);
Label label14 = new Label();
methodVisitor.visitLabel(label14);
methodVisitor.visitLineNumber(30, label14);
methodVisitor.visitVarInsn(ALOAD, 2);
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/RuntimeException", "printStackTrace", "()V", false);
Label label15 = new Label();
methodVisitor.visitLabel(label15);
methodVisitor.visitLineNumber(31, label15);
methodVisitor.visitVarInsn(ALOAD, 2);
methodVisitor.visitInsn(ATHROW);
methodVisitor.visitLabel(label3);
methodVisitor.visitLineNumber(32, label3);
methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Exception"});
methodVisitor.visitVarInsn(ASTORE, 2);
Label label16 = new Label();
methodVisitor.visitLabel(label16);
methodVisitor.visitLineNumber(33, label16);
methodVisitor.visitTypeInsn(NEW, "java/lang/RuntimeException");
methodVisitor.visitInsn(DUP);
methodVisitor.visitVarInsn(ALOAD, 2);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/Throwable;)V", false);
methodVisitor.visitInsn(ATHROW);
Label label17 = new Label();
methodVisitor.visitLabel(label17);
methodVisitor.visitLocalVariable("var5", "Ljava/lang/Process;", null, label9, label2, 5);
methodVisitor.visitLocalVariable("var6", "Lorg/n1es/common/hook/HookResult;", null, label10, label2, 6);
methodVisitor.visitLocalVariable("var7", "Z", null, label11, label2, 7);
methodVisitor.visitLocalVariable("var2", "[Ljava/lang/Object;", null, label4, label2, 2);
methodVisitor.visitLocalVariable("var3", "Lorg/n1es/common/hook/HookResult;", null, label5, label2, 3);
methodVisitor.visitLocalVariable("var4", "Z", null, label6, label2, 4);
methodVisitor.visitLocalVariable("var8", "Ljava/lang/RuntimeException;", null, label14, label3, 2);
methodVisitor.visitLocalVariable("e", "Ljava/lang/Exception;", null, label16, label17, 2);
methodVisitor.visitLocalVariable("this", "Lorg/n1es/asm/RuntimeHook;", null, label0, label17, 0);
methodVisitor.visitLocalVariable("var1", "Ljava/lang/String;", null, label0, label17, 1);
methodVisitor.visitMaxs(9, 8);
methodVisitor.visitEnd();
}
classWriter.visitEnd();
return classWriter.toByteArray();
有了字节码的基础后,就可以找到自己需要的生成代码,还挺方便
Java虚拟机执行模型
Java代码是在线程内部执行的。每个线程都有自己的执行栈,栈由帧组成。每个帧表示一个方法调用:每次调用一个方法时,会将有一个新帧压入当前线程的执行栈。当方法正常返回或异常返回时,会将这个帧从执行栈中弹出(执行POP指令),执行过程在发出调用的方法中继续执行(这个方法的帧现在位于栈的顶端)。
每一个帧包括俩部分:一个局部变量部分和一个操作数栈部分。局部变量部分包含可根据索引以随机顺序访问的变量。由名字可以看出,操作数栈部分是一个栈,其中包含了供字节码指令用作操作数的值。这意味着这个栈中的值只能按照后入先出的顺序访问。不能把操作数栈和线程的执行栈相混淆:执行栈中的每一帧都包含自己的操作数栈。
MethodVisitor
visitCode和visitMaxs方法可用于检测该方法的字节代码在一个事件序列中的开始与结束。和类的情况一样,visitEnd方法也必须在最后调用,用于检测一个方法在一个事件序列中的结束。在ClassVisitor没有调用visitEnd方法时,MethodVisitor中的实例是完全独立的,可按任意顺序访问方法。
ASM提供了三哥基于MethodVisitor API的核心组件,用于生成和转换方法:
ClassReader类分析已编译方法的内容,在其accept方法的参数中传送了ClassVisitor,
ClassReader类将针对这一ClassVisitor返回的MethodVIsitor对象调用相应方法。
ClassWriter的visitMethod方法返回MethodVisitor接口的一个实现,它直接以二进制形式生成已编译方法。
MethodVisitor 类将它接收到的所有方法调用委托给另一个 MethodVisitor 方
法。 可以将它看作一个事件筛选器。
在ClassWriter中需要计算所有帧,如果使用new ClassWriter(0)创建一个类时,需要计算帧、局部变量与操作数栈的大小。使用 visitFrame()
插入帧(第一个参数用于压缩栈映射帧,以减少类文件的大小。JVM在验证字节码时,会根据这些帧类型来推断当前帧的状态),在方法结束前调用visitMaxs()计算最大栈深度、局部变量表大小。需要注意:在适当的位置调用visitFrame
插入栈映射帧,确保每个跳转目标、异常处理块开始等位置都有帧。适用于对性能比较敏感的环境。推介使用COMPUTE_FRAMES自动处理所有帧,会有轻微的性能开销,但可以避免计算错误。(COMPUTE_MAXS选项会使ClassWriter的速度降低10%左右,使用COMPUTE_FRAMES会降低一半)
mv.visitFrame(
Opcodes.F_NEW, // 帧类型
localCount, // 局部变量表大小
localTypes, // 局部变量类型数组
stackCount, // 操作数栈大小
stackTypes // 操作数栈类型数组
);
帧类型(Frame Type)
类型 | 说明 |
---|---|
F_NEW |
完整描述帧(推荐使用) |
F_SAME |
与前一帧相同 |
F_SAME1 |
栈比前一帧多1个元素 |
F_APPEND |
局部变量表比前一帧多k个元素 |
F_CHOP |
局部变量表比前一帧少k个元素 |
F_FULL |
完整描述帧(同F_NEW) |