写在前面
本文看下如何通过asm生成变量并sout。
1:代码
直接看代码吧,注释很详细,有不懂的,留言告诉我:
package com.dahuyuo.asmtest;import org.objectweb.asm.*;
import org.objectweb.asm.commons.AdviceAdapter;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;import static org.objectweb.asm.Opcodes.ASM5;public class YY extends ClassLoader {public static void main(String[] args) throws Exception {ClassReader cr = new ClassReader("com.dahuyuo.asmtest.XX");ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);cr.accept(new ClassVisitor(ASM5, cw) {public MethodVisitor visitMethod(int access, String name, Stringdescriptor, String signature, String[] exceptions) {// 方法过滤if (!"didi".equals(name)) {return super.visitMethod(access, name, descriptor,signature, exceptions);}MethodVisitor mv = super.visitMethod(access, name, descriptor,signature, exceptions);return new AdviceAdapter(ASM5, mv, access, name, descriptor) {protected void onMethodEnter() {
// super.onMethodEnter();// 这里不管定义啥类型都可以用Type.LONG_TYPE,没搞懂是干啥的int intVar1 = newLocal(Type.INT_TYPE); // 创建一个int类型的局部变量 相当于int var;mv.visitLdcInsn(99); // 将常量99加载到操作数栈的栈顶mv.visitVarInsn(ISTORE, intVar1); // 将栈顶值赋值给本地变量 int var = 99;mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); // 将静态变量System.out压倒栈顶mv.visitVarInsn(ILOAD, intVar1); // 将整数变量压到栈顶mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false); // 出栈System.out,整数变量,并调用sout(int)完成输出
//// int intVar2 = newLocal(Type.INT_TYPE);
// mv.visitTypeInsn(NEW, "java/lang/String"); // new String()然后推到栈顶
// mv.visitInsn(DUP); // 复制栈顶元素int nextLocal = this.nextLocal; // 获取当前可用的局部变量表位置??mv.visitLdcInsn("pppp"); // 加载常量到栈顶mv.visitVarInsn(ASTORE, nextLocal); // 将栈顶string对象复制给局部变量
// mv.visitMethodInsn(INVOKESPECIAL, "java/lang/String", "<init>", "(Ljava/lang/String;)V", false); // 调用构造函数完成string对象创建
// mv.visitVarInsn(ASTORE, this.nextLocal);mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); // 将静态变量System.out压倒栈顶mv.visitVarInsn(ALOAD, nextLocal); // 将string变量压到栈顶mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); // 出栈System.out,整数变量,并调用sout(string)完成输出}public void visitMaxs(int maxStack, int maxLocals) {super.visitMaxs(maxStack, maxLocals);}protected void onMethodExit(int opcode) {super.onMethodExit(opcode);}};}}, ClassReader.EXPAND_FRAMES);// 获取后的字节码byte[] byteAfterInstrument = cw.toByteArray();outputClazz(byteAfterInstrument, "xxvv");// 测试方法Class<?> clazz = newYY().defineClass("com.dahuyuo.asmtest.XX", byteAfterInstrument, 0, byteAfterInstrument.length);Method didi = clazz.getMethod("didi");didi.invoke(clazz.newInstance());}private static void outputClazz(byte[] bytes, String className) {// 输出类字节码FileOutputStream out = null;try {String pathName = YY.class.getResource("/").getPath() + className + "_after_instrument.class";out = new FileOutputStream(new File(pathName));System.out.println("插桩后代码输出路径:" + pathName);out.write(bytes);} catch (Exception e) {e.printStackTrace();} finally {if (null != out) try {out.close();} catch (IOException e) {e.printStackTrace();}}}
}
用来进行插桩的XX类如下:
public class XX {/* public static void main(String[] args) {int a = 10;int b = 20;System.out.println(a + b);}*/public void didi() {}
}
运行测试:
查看生成的字节码:
就是我们要的效果。
写在后面
参考文章列表
JVM 虚拟机字节码指令表 。