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 表示 booleanC 表示 charB 表示 byteS 表示

shortI 表示 intF 表示 floatJ 表示 longD 表示 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)