- 1 概述
- 2 类加载
- 3 字节码
- 4 简单使用
https://mp.weixin.qq.com/s/wDwU7ndRwHdxqIIR43YNuQ
ASM: https://asm.ow2.io/
Guid: https://www.baeldung.com/java-asm
极客时间:https://github.com/AndroidAdvanceWithGeektime/Chapter-ASM
1 概述
-
背景:高效修改字节码,编码 source code 中的功能需要拓展,修改 、增强
-
ASM :字节码修改工具,可以修改class文件,实现在 非运行期 修改字节码
-
ASM 和平台无关提供修改字节码的能力
-
场景
- 代码插桩
- 性能优化
- 热修复
- AOP编程
2 类加载
方法区: 保存 Class 相关信息,同类存一份,method等相关信息
自定义类加载器
3 字节码
操作数入栈
操作符入栈
(最底层控制线程栈执行方法的寄存器 ebp esp)
一个个方法都构成一个个 栈帧 运行在线程
线程本身也是以栈来执行方法
线程栈最下面就是main 或者 run
然后调用的方法会在其上方入栈执行
入栈方法占用栈空间,执行结束从线程栈pop
一段代码被多个线程执行,那么其中的寄存器,或者内存中的存储变量就会被多个线程同时操作,出现并发问题
对于修改字节码,可以使用工具直接生成修改的代码
IDE Plugin : ASM ByteCode Outline
java code
public class MainActivity {
private int id;
public MainActivity(int id) {
this.id = id;
}
@Override
public String toString() {
return super.toString();
}
public int sum(int l, int r){
int result = l + r;
return result;
}
}
bytecode
// class version 49.0 (49)
// access flags 0x21
public class MainActivity {
// compiled from: MainActivity.java
// access flags 0x2
private I id
// access flags 0x1
public <init>(I)V
L0
LINENUMBER 5 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 6 L1
ALOAD 0
ILOAD 1
PUTFIELD MainActivity.id : I
L2
LINENUMBER 7 L2
RETURN
L3
LOCALVARIABLE this LMainActivity; L0 L3 0
LOCALVARIABLE id I L0 L3 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1
public toString()Ljava/lang/String;
L0
LINENUMBER 11 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.toString ()Ljava/lang/String;
ARETURN
L1
LOCALVARIABLE this LMainActivity; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public sum(II)I
L0
LINENUMBER 15 L0
ILOAD 1
ILOAD 2
IADD
ISTORE 3
L1
LINENUMBER 16 L1
ILOAD 3
IRETURN
L2
LOCALVARIABLE this LMainActivity; L0 L2 0
LOCALVARIABLE l I L0 L2 1
LOCALVARIABLE r I L0 L2 2
LOCALVARIABLE result I L1 L2 3
MAXSTACK = 2
MAXLOCALS = 4
}
ASMifed
import java.util.*;
import org.objectweb.asm.*;
public class MainActivityDump implements Opcodes {
public static byte[] dump() throws Exception {
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;
cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER, "MainActivity", null, "java/lang/Object", null);
cw.visitSource("MainActivity.java", null);
{
fv = cw.visitField(ACC_PRIVATE, "id", "I", null, null);
fv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(I)V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(5, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLineNumber(6, l1);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ILOAD, 1);
mv.visitFieldInsn(PUTFIELD, "MainActivity", "id", "I");
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitLineNumber(7, l2);
mv.visitInsn(RETURN);
Label l3 = new Label();
mv.visitLabel(l3);
mv.visitLocalVariable("this", "LMainActivity;", null, l0, l3, 0);
mv.visitLocalVariable("id", "I", null, l0, l3, 1);
mv.visitMaxs(2, 2);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(11, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "toString", "()Ljava/lang/String;", false);
mv.visitInsn(ARETURN);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLocalVariable("this", "LMainActivity;", null, l0, l1, 0);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "sum", "(II)I", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(15, l0);
mv.visitVarInsn(ILOAD, 1);
mv.visitVarInsn(ILOAD, 2);
mv.visitInsn(IADD);
mv.visitVarInsn(ISTORE, 3);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLineNumber(16, l1);
mv.visitVarInsn(ILOAD, 3);
mv.visitInsn(IRETURN);
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitLocalVariable("this", "LMainActivity;", null, l0, l2, 0);
mv.visitLocalVariable("l", "I", null, l0, l2, 1);
mv.visitLocalVariable("r", "I", null, l0, l2, 2);
mv.visitLocalVariable("result", "I", null, l1, l2, 3);
mv.visitMaxs(2, 4);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
}
4 简单使用
通过一个例子简单学习ASM的使用
目的:在指定方法执行之前 插入一行代码
修改前
public class MainActivity {
@Override
public String toString() {
return super.toString();
}
}
修改后
public class MainActivity {
public String toString() {
System.out.println("插入:Hello World!");
return super.toString();
}
}
实现:
/**
* @author gongshijie
*
*/
public class Main {
public static void main(String[] args) {
try {
// 修改字节码
ClassReader classReader = new ClassReader(MainActivity.class.getName());
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
ClassVisitor classVisitor = new LogVisitor(Opcodes.ASM5, classWriter);
classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
byte[] bytecode = classWriter.toByteArray();
// 修改后的结果
FileOutputStream fos = new FileOutputStream(new File("MainActivity.class"));
fos.write(bytecode);
fos.flush();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* @author gongshijie
*/
public class MainActivity {
@Override
public String toString() {
return super.toString();
}
}
/**
* @author gongshijie
* 遍历方法 定位 目标方法
*/
public class LogVisitor extends ClassVisitor {
public LogVisitor(int i) {
super(i);
}
public LogVisitor(int i, ClassVisitor classVisitor) {
super(i, classVisitor);
}
@Override
public MethodVisitor visitMethod(int i, String s, String s1, String s2, String[] strings) {
MethodVisitor methodVisitor = cv.visitMethod(i, s, s1, s2, strings);
if (s.equals("toString")) {
// 若是我们需要增强的方法 toString() 就进入我们的 visitor 操作字节码
methodVisitor = new LogAdapter(Opcodes.ASM5, methodVisitor, i, s, s1);
}
return methodVisitor;
}
}
/**
* @author gongshijie
* 对 目标方法 进行字节码修改
*/
public class LogAdapter extends AdviceAdapter {
protected LogAdapter(int i, MethodVisitor methodVisitor, int i1, String s, String s1) {
super(i, methodVisitor, i1, s, s1);
}
@Override
protected void onMethodEnter() {
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("\u63d2\u5165\uff1aHello World!");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
}
结果
总之, ASM提供修改字节码的能力,怎么使用可以结合场景
不会带来性能变化,在 编译期 完成字节码操作
网友评论