美文网首页
字节码插装统计

字节码插装统计

作者: ZoranLee | 来源:发表于2026-01-28 13:14 被阅读0次

参考文档:

JVM字节码

javap

  • javap 核心命令解析
命令 作用
javap TestClass.class 基础输出:类的访问修饰符、字段、方法签名
javap -v TestClass.class 详细输出(最常用):魔数、常量池、访问标志、字段表、方法表、字节码指令等
javap -c TestClass.class 仅输出方法的字节码指令
javap -s TestClass.class 输出字段 / 方法的签名(包含类型描述符)

魔数:

Class 文件开头 4 个字节固定为0xCAFEBABE(是识别 Class 文件的标志)

常量池:

Constant pool部分,存储所有字符串、类名、方法名等常量,通过索引引用(如#1指向java/lang/Object.<init>());

Constant pool:  // 常量池(Class文件的核心,存储字符串、类名、方法名等)
   #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
   #2 = String             #16            // JavaBytecode
   #3 = Fieldref           #5.#17         // TestClass.name:Ljava/lang/String;
   #4 = Methodref          #5.#15         // TestClass."<init>":()V
   #5 = Class              #18            // TestClass
   #6 = Class              #19            // java/lang/Object

访问标志:

如ACC_PUBLIC(公有的)、ACC_PRIVATE(私有的),标识类 / 字段 / 方法的修饰符;

类型描述符:

如(II)I表示 “入参 2 个 int,返回值 int”,Ljava/lang/String;表示 String 类型(引用类型以 L 开头,分号结尾);

字节码指令:

如iload_1(加载第一个 int 参数到操作数栈)、iadd(操作数栈顶两个 int 相加)、ireturn(返回 int 值)。

常用字节码指令

类别 核心作用 常用指令及说明
★数据操作指令 局部变量↔操作数栈的交互 1、iload_n:把局部变量表中第 n 个 int 变量压入栈(n=0/1/2/3,如iload_1)2、 istore_n:把栈顶 int 值存入局部变量表第 n 位 3、 ldc:把常量池中的常量(字符串 /int/float)压入栈 4、 aload_0:把当前对象(this)压入栈(引用类型)
★算术运算指令 操作数栈上的数值计算 1、 iadd:栈顶两个 int 相加,结果压回栈 2、 isub/imul/idiv:int 减 / 乘 / 除 3、 ladd/dadd:long/double 相加(不同类型指令不同)4、 ineg:int 取反
★方法调用指令 调用类 / 对象的方法 1、 invokestatic:调用静态方法(如Math.abs())2、 invokevirtual:调用实例方法(如obj.toString())3、 invokespecial:调用构造方法 / 私有方法 4、 invokeinterface:调用接口方法
控制流指令 实现分支、循环、异常 1、ifeq:栈顶 int 为 0 则跳转(if (x==0))2、 ifne:栈顶 int 非 0 则跳转 3、 goto:无条件跳转(实现循环)4、 tableswitch:switch-case(整数类型)5、 athrow:抛出异常
类型转换指令 不同数值类型的转换 1、 i2l:int 转 long 2、 l2d:long 转 double 3、i2b:int 转 byte(JVM 中 byte/short 会先转 int 处理)
对象操作指令 创建对象、访问字段 1、new:创建对象(如new TestClass)2、 getfield:获取对象的实例字段(如obj.name)3、 putfield:给对象的实例字段赋值 4、 getstatic:获取静态字段
栈操作指令 操作数栈的管理 1、pop:弹出栈顶 1 个元素2、 dup:复制栈顶元素并压入栈(如构造方法中复用 this)3、swap:交换栈顶两个元素
返回指令 方法返回值 1、 ireturn:返回 int 类型 2、 lreturn:返回 long 类型3、 areturn:返回引用类型 4、 return:无返回值(void 方法)

示例

使用ASM操作字节码实现创建类和方法、改写类和方法、实现接口、继承父类覆写方法等

  • 创建一个类
public class User {
    private String name;
    
    public User() {
        this.name = "default";
    }
    
    public String getName() {
        return this.name;
    }
}
import org.objectweb.asm.*;
import java.io.FileOutputStream;

public class ASMCreateClassDemo {
    public static void main(String[] args) throws Exception {
        // 1. 创建ClassWriter:COMPUTE_FRAMES自动计算栈映射帧,COMPUTE_MAXS自动计算栈大小和局部变量表大小
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
        
        // 2. 定义类的基础信息:版本、访问标志、类名、泛型签名、父类、实现的接口
        // 参数说明:
        // V1_8:JDK8版本 | ACC_PUBLIC:公有的 | "com/example/User":类的内部名(.替换为/)
        // null:无泛型 | "java/lang/Object":父类(默认继承Object) | new String[0]:无实现接口
        cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "com/example/User", null, "java/lang/Object", null);

        // 3. 添加私有字段:private String name
        FieldVisitor fv = cw.visitField(
                Opcodes.ACC_PRIVATE,  // 访问标志:私有
                "name",               // 字段名
                "Ljava/lang/String;", // 字段类型描述符(String类型)
                null,                 // 泛型签名
                null                  // 默认值
        );
        fv.visitEnd(); // 字段定义结束

        // 4. 添加无参构造方法:public User() { this.name = "default"; }
        MethodVisitor mv = cw.visitMethod(
                Opcodes.ACC_PUBLIC,  // 访问标志:公有
                "<init>",            // 构造方法固定名为<init>
                "()V",               // 方法描述符:无参,返回void
                null,                // 泛型签名
                null                 // 异常表
        );
        // 构造方法必须先调用父类构造方法
        mv.visitCode(); // 开始编写方法体字节码
        mv.visitVarInsn(Opcodes.ALOAD_0); // 加载this对象到操作数栈
        // 调用父类Object的构造方法
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
        
        // 给name字段赋值:this.name = "default"
        mv.visitVarInsn(Opcodes.ALOAD_0); // 加载this
        mv.visitLdcInsn("default");       // 加载常量"default"到栈
        // 给实例字段赋值:putfield 类名/字段名/类型描述符
        mv.visitFieldInsn(Opcodes.PUTFIELD, "com/example/User", "name", "Ljava/lang/String;");
        
        mv.visitInsn(Opcodes.RETURN);     // 方法返回(void)
        mv.visitMaxs(0, 0);               // 栈大小和局部变量表大小(COMPUTE_MAXS会自动计算,填0即可)
        mv.visitEnd(); // 构造方法结束

        // 5. 添加getName方法:public String getName() { return this.name; }
        MethodVisitor getNameMv = cw.visitMethod(
                Opcodes.ACC_PUBLIC,
                "getName",
                "()Ljava/lang/String;", // 无参,返回String
                null,
                null
        );
        getNameMv.visitCode();
        getNameMv.visitVarInsn(Opcodes.ALOAD_0); // 加载this
        // 获取实例字段值:getfield
        getNameMv.visitFieldInsn(Opcodes.GETFIELD, "com/example/User", "name", "Ljava/lang/String;");
        getNameMv.visitInsn(Opcodes.ARETURN); // 返回引用类型(String)
        getNameMv.visitMaxs(0, 0);
        getNameMv.visitEnd();

        // 6. 类定义结束
        cw.visitEnd();

        // 7. 生成字节码数组并写入文件(保存为User.class)
        byte[] classBytes = cw.toByteArray();
        try (FileOutputStream fos = new FileOutputStream("User.class")) {
            fos.write(classBytes);
        }
        System.out.println("类创建成功!字节码已写入User.class");
    }
}

改写已有类

import org.objectweb.asm.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class ASMModifyClassDemo {
    public static void main(String[] args) throws Exception {
        // 1. 读取原有User.class的字节码
        byte[] originalClassBytes;
        try (FileInputStream fis = new FileInputStream("User.class")) {
            originalClassBytes = fis.readAllBytes();
        }

        // 2. 创建ClassReader(读)+ ClassWriter(写)+ ClassVisitor(修改)
        ClassReader cr = new ClassReader(originalClassBytes);
        // ClassWriter的参数:cr表示复用原有类的常量池,减少字节码体积
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
        
        // 自定义ClassVisitor,拦截getName方法
        ClassVisitor cv = new ClassVisitor(Opcodes.ASM9, cw) {
            // 重写visitMethod,拦截方法定义
            @Override
            public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
                // 只拦截getName方法
                if ("getName".equals(name) && "()Ljava/lang/String;".equals(desc)) {
                    // 获取原方法的MethodVisitor,并用自定义MethodVisitor包装
                    MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
                    return new MethodVisitor(Opcodes.ASM9, mv) {
                        // 重写visitCode,替换原有方法体
                        @Override
                        public void visitCode() {
                            // 新的方法逻辑:return this.name + "_modified"
                            mv.visitVarInsn(Opcodes.ALOAD_0); // 加载this
                            mv.visitFieldInsn(Opcodes.GETFIELD, "com/example/User", "name", "Ljava/lang/String;");
                            mv.visitLdcInsn("_modified"); // 加载常量"_modified"
                            // 调用String的concat方法:String.concat(String)
                            mv.visitMethodInsn(
                                    Opcodes.INVOKEVIRTUAL,
                                    "java/lang/String",
                                    "concat",
                                    "(Ljava/lang/String;)Ljava/lang/String;",
                                    false
                            );
                            mv.visitInsn(Opcodes.ARETURN); // 返回拼接后的字符串
                            mv.visitMaxs(0, 0);
                        }
                    };
                }
                // 其他方法保持不变
                return super.visitMethod(access, name, desc, signature, exceptions);
            }
        };

        // 3. 读取并修改类
        cr.accept(cv, ClassReader.EXPAND_FRAMES);

        // 4. 写入修改后的字节码
        byte[] modifiedClassBytes = cw.toByteArray();
        try (FileOutputStream fos = new FileOutputStream("User_modified.class")) {
            fos.write(modifiedClassBytes);
        }
        System.out.println("类修改成功!字节码已写入User_modified.class");
    }
}

相关文章

  • Android 如何切换到 Transform API?

    摘要: 如果你的 Android 构建中涉及到字节码插装(bytecode instrumentation),或者...

  • 自定义Gradle插件

      最近在学习字节码插桩技术,利用字节码插桩技术,我们可以在编译时期对字节码进行修改,达到完成一些特殊需求,比如埋...

  • 注解 - 插桩,编译后处理筛选

    什么是插桩? 插桩就是将一段代码插入或者替换原本的代码。字节码插桩顾名思义就是在我们编写的源码编译成字节码(Cla...

  • 注解的使用(二):插桩,编译后处理筛选

    什么是插桩? 插桩就是将一段代码插入或者替换原本的代码。字节码插桩顾名思义就是在我们编写的源码编译成字节码(Cla...

  • 从EMMA说起

    EMMA 是一个开源、面向 Java 程序测试覆盖率收集和报告工具。它通过对编译后的 Java 字节码文件进行插装...

  • android字节码插桩

    自定义插件 目前,Android项目基本都是使用Gradle去构建,在学习插桩之前先对Gradle插件知识有基本的...

  • ASM字节码插桩

    个人博客http://www.milovetingting.cn ASM字节码插桩 前言 热修复的多Dex加载方案...

  • Javassist 字节码插桩

    Javassist基础 Javassist 使您可以 检查、编辑以及创建Java 二进制类。Javassist 使...

  • JVM与DVM ——(4)如何编译插桩操纵字节码

    本文用于记录:如何编译插桩操纵字节码。 之前学了 Java 字节码文件的格式,并通过一个 demo 手动模拟了 J...

  • java字节码增强调研笔记

    字节码增强,实质就是在编译期或运行期进行 字节码插桩 ,以便在运行期影响程序的 执行行为 。按照增强时机,可以分为...

网友评论

      本文标题:字节码插装统计

      本文链接:https://www.haomeiwen.com/subject/aeuclstx.html