一 ASM介绍

ASM是一个通用的Java字节码操作和剖析框架。它可用于修正现有类直接以二进制办法动态生成类ASM提供了一些常见的字节码转换和剖析算法,能够根据这些算法构建定制的杂乱转换和代码剖析工具。

ASM提供了与其他Java字节码框架相似的功能,但更关注功能。因为它的设计和完成尽或许的小和快,它十分适合在动态系统中运用(但当然也能够以静态的办法运用,例如在编译器中)。关于ASM更多介绍,能够拜见 ASM官网。

ASM 从组成结构上能够分红两部分,一部分为Core API,另一部分为Tree API

  • Core API 包含asm.jarasm-util.jarasm-commons.jar
  • Tree API 包含 asm-tree.jarasm-analysis.jar

本文首要讲解Tree API 的相关运用。

二 Tree API

咱们知道Class字节码 是由无符号数两种数据结构组成。而 ASM Tree API 能够认为是对上面两种结构进行了进一步封装以简化运用。

运用时首先需求引进 tree api,这儿以9.2为例,最新版本请查看官网 :

implementation 'org.ow2.asm:asm-commons:9.2' //Tree API

通过 ./gradlew app:dependencies 查看依靠关系:

+--- org.ow2.asm:asm-commons:9.2
|    +--- org.ow2.asm:asm:9.2
|    +--- org.ow2.asm:asm-tree:9.2
|    |    \--- org.ow2.asm:asm:9.2
|    \--- org.ow2.asm:asm-analysis:9.2
|         \--- org.ow2.asm:asm-tree:9.2 (*)

其间 org.ow2.asm:asm-tree:9.2 的内部类有:

一起来学字节码插桩:ASM Tree API
首要用到的类有:

  • ClassNode
    • FieldNode
    • MethodNode
      • InsnList
        • AbstractInsnNode
        • TypeInsnNode
        • VarInsnNode
        • FieldInsnNode
        • MethodInsnNode
        • IincInsnNode
        • InsnNode
        • IntInsnNode
        • InvokeDynamicInsnNode
        • JumpInsnNode
        • LabelNode
        • LdcInsnNode
        • LookupSwitchInsnNode
        • MultiANewArrayInsnNode
        • TableSwitchInsnNode
      • TryCatchBlockNode

用类图表明为

一起来学字节码插桩:ASM Tree API

整理一下他们之间的关系:

  • ClassNode 包含 FieldNodeMethodNode
  • MethodNode 中包含有序指令集合InsnList 及其反常处理TryCatchBlockNode
  • InsnList 由很多个有序的单指令 AbstractInsnNode 组合而成。
  • AbstractInsnNode是一个抽象类,表明字节码指令的节点。一条指令最多只能在一个 InsnList 中出现一次。AbstractInsnNode的子类完成如下:
    一起来学字节码插桩:ASM Tree API
    下面别离来看下每个类的意义。

2.1、ClassNode

ClassNode的注释为A node that represents a class,表明一个类的节点。能够通俗理解为:一个Class类经过Tree API解析后,能够将其转换成一个ClassNode(内部包含类的一切信息)。

public class ClassNode extends ClassVisitor {
  public int version;
  public int access;
  public String name;
  public String signature;
  public String superName;
  public List<String> interfaces;
  public String outerClass;
  public String outerMethod;
  public List<Attribute> attrs;
  public List<InnerClassNode> innerClasses;
  public List<FieldNode> fields;
  public List<MethodNode> methods;
}

ClassNode承继自ClassVisitor,能够看到ClassNode内部的字段正好对应Class中的一切信息。

类型 称号 阐明
int version jdk版本
int access 拜访级
String name 类名,选用全地址,如java/lang/String
String signature 签名,通常是null
String superName 父类类名,选用全地址
List interfaces 完成的接口,选用全地址
String sourceFile 源文件,或许为null
String sourceDebug debug源,或许为null
String outerClass 外部类
String outerMethod 外部办法
String outerMethodDesc 外部办法描绘(包含办法参数、回来值)
List visibleAnnotations 可见的注解
List invisibleAnnotations 不行见的注解
List attrs 类的Attribute
List innerClasses 内部类列表
List fields 字段列表
List methods 办法列表
2.1.1、accept(ClassVisitor classVisitor)

ClassNode#accept(classVisitor) 传入一个ClassVisitor,内部完成如下:

  // Makes the given class visitor visit this class.
  public void accept(final ClassVisitor classVisitor) {
    // Visit the header.
    String[] interfacesArray = new String[this.interfaces.size()];
    //......
    // Visit the fields.
    for (int i = 0, n = fields.size(); i < n; ++i) {
      fields.get(i).accept(classVisitor);
    }
    // Visit the methods.
    for (int i = 0, n = methods.size(); i < n; ++i) {
      methods.get(i).accept(classVisitor);
    }
    classVisitor.visitEnd();
  }

看下这个办法的注释:Makes the given class visitor visit this classaccept(classVisitor) 能够让传入的classVisitor拜访ClassNode中的一切内容。而ClassWriter承继自ClassVisitor,那么就能够将ClassWriter作为入参传入ClassNode中,进而调用classNode.accept(classWriter) ClassNode中的数据传入ClassWriter中,终究调用classWriter.toByteArray()ClassNode转为byte[]

示例:

//自定义ClassNode类
class AOutLibClassNode(val api: Int, private val classWriter: ClassWriter) : ClassNode(api) {
    override fun visitEnd() {
        //允许classWriter拜访ClassNode类中的信息
        accept(classWriter)
    }
}
ClassReader classReader = new ClassReader(xxx.class.getName());
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
//1、将ClassWriter传入ClassNode中
ClassNode classNode = new AOutLibClassNode(BConstant.ASM9, classWriter);
classReader.accept(classNode, ClassReader.EXPAND_FRAMES);
//2、ClassNode -> ClassWriter -> bytes[]
bytes[] classNodeToBytes = classWriter.toByteArray();
2.1.2、FieldNode
public class FieldNode extends FieldVisitor {
  public int access;
  public String name;
  public String desc;
  public String signature;
  public Object value; //字段的初始化值
  public List<AnnotationNode> visibleAnnotations;
  public List<AnnotationNode> invisibleAnnotations;
  public List<TypeAnnotationNode> visibleTypeAnnotations;
  public List<TypeAnnotationNode> invisibleTypeAnnotations;
  public List<Attribute> attrs;
  //构造办法
  public FieldNode( final int api, final int access, final String name,
      final String descriptor, final String signature, final Object value) {
         super(api);
         this.access = access;
         this.name = name;
         this.desc = descriptor;
         this.signature = signature;
         this.value = value;
   } 
}

FieldNode承继自FieldVisitor,能够用于遍历或生成字段,内部变量阐明:

类型 称号 阐明
int access 拜访级
String name 字段名
String signature 签名,通常是 null
String desc 类型描绘,例如 Ljava/lang/String、D(double)、F(float)Objectvalue初始值,通常为 null
List visibleAnnotations 可见的注解
List invisibleAnnotations 不行见的注解
List attrs 字段的 Attribute

FieldNode生成代码示例:

ClassNode node = new ClassNode(Opcodes.ASM9);
//public + final + static
int access = Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL| Opcodes.ACC_STATIC;
//long类型,value设置为1
node.fields.add(new FieldNode(access, "timer", "J",null, 1)); 
//ClassWriter转化为byte[]
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
node.accept(writer);
byte[] byteArr = writer.toByteArray();

履行成果:

public static final long timer = 1;

能够看到生成了新的字段。

2.1.3、MethodNode
public class MethodNode extends MethodVisitor {
  //1、标识办法的基本特点:办法头
  public int access;
  public String name; //办法名
  public String desc;
  public String signature;
  public List<String> exceptions;
  //2、标识办法的指令集合:办法体
  public InsnList instructions; //办法中的有序指令集
  public List<TryCatchBlockNode> tryCatchBlocks;//办法中的try catch反常处理数组
  public List<ParameterNode> parameters;
  public int maxStack; // 栈最大值
  public int maxLocals; //局部变量的最大值
}

MethodNode用于拜访或修正办法。比较于FieldNode的单一,MethodNode内部或许触及多条指令,办法履行时,也会触及到局部变量表操作数栈,详细可拜见:Java内存模型。MethodNode 内部变量阐明:

类型 称号 阐明
int access 办法拜访级
String name 办法名
String desc 办法描绘,其包含办法的回来值、参数
String signature 泛型签名,通常是null
List exceptions 或许回来的反常列表
List visibleAnnotations 可见的注解列表
List invisibleAnnotations 不行见的注解列表
List attrs 办法的Attribute列表
Object annotation Default默许的注解
List visibleParameterAnnotations 可见的参数注解
List invisibleParameterAnnotations 不行见的参数注解列表
InsnList instructions Opcode操作码列表
List tryCatchBlock try-catch块列表
int maxStack 最大操作数栈的深度
int maxLocals 最大局部变量表的大小
List localVariables 本地(局部)变量节点列表

MethodNode 承继自 MethodVisitor,所以内部会有各种 visitxxx() 办法,而在调用MethodNode#visitxxx() 办法后,会将指令存储到instructions (InsnList)

MethodNode.InsnList
// InsnList本质上是一个双链表
public class InsnList implements Iterable<AbstractInsnNode> {
  private int size; //链表内数据的size
  private AbstractInsnNode firstInsn; //链表头指针
  private AbstractInsnNode lastInsn; //链表尾指针
}

InsnList类完成了Iterable< AbstractInsnNode>接口,内部存储了办法履行时的指令集。其间 AbstractInsnNode 表明单条指令,而InsnList类是一个有序存储AbstractInsnNode指令的双向链表。

当咱们想刺进操作码指令从而达到修正字节码时,能够对InsnList进行如下操作

  • add(final AbstractInsnNode insnNode):将给定的insnNode指令添加到InsnList列表的末尾;
  • add(final InsnList insnList):将一组指令添加到InsnList列表的末尾;
  • insert(final AbstractInsnNode insnNode):将给定的insnNode指令添加到InsnList列表的最初;
  • insert(final InsnList insnList):将一组指令添加到InsnList列表的最初;
  • insert(final AbstractInsnNode previousInsn, final AbstractInsnNode insnNode):将insnNode指令刺进到previousInsn指令之后;
  • insert(final AbstractInsnNode previousInsn, final InsnList insnList):将insnList多个指令刺进到previousInsn指令之后;
  • insertBefore(final AbstractInsnNode nextInsn, final AbstractInsnNode insnNode):将insnNode指令刺进到nextInsn之前;
  • insertBefore(final AbstractInsnNode nextInsn, final InsnList insnList): 将insnList多个指令刺进到nextInsn之前。

持续看AbstractInsnNode

public abstract class AbstractInsnNode {
    protected int opcode; //当时指令
    AbstractInsnNode previousInsn; //指向上一个指令
    AbstractInsnNode nextInsn; //指向下一个指令
    int index; //当时指令在InsnList中的索引
}

AbstractInsnNode能够表明的指令集合:

类型 称号 阐明
FieldInsnNode GETFIELD、PUTFIELD 等变量操作的字节码 操作变量
FrameNode 栈映射帧
IincInsnNode 用于 IINC 变量自加操作 int var:方针局部变量的位置 int incr: 要增加的数
InsnNode 无参数值操作的字节码,如 ALOAD_0
IntInsnNode 用于 BIPUSH、SIPUSH 和 NEWARRAY 这三个直接操作整数的操作 int operand:操作的整数值
InvokeDynamicInsnNode 用于 Java7 新增的 INVOKEDYNAMIC 操作的字节码 String name:办法称号; String desc:办法描绘; Handle bsm:句柄; Object bsmArgs:参数常量
JumpInsnNode 用于 IFEQ 或 GOTO 等跳转操作 LabelNode lable:方针lable
LabelNode 用于表明跳转点的 Label 节点
LdcInsnNode LDC 用于加载常量池中引证值并进行刺进 Object cst:引证值
LineNumberNode 表明行号的节点 int line:行号;LabelNode start:对应的第一个
LabelLookupSwitchInsnNode 用于完成 LOOKUPSWITCH 操作的字节码 LabelNode dflt:default 块对应的 LableList keys 键列表;List labels:对应的 Label 节点列表
MethodInsnNode 用于 INVOKEVIRTUAL 等传统办法调用操作的字节码 不适用于 Java7 新增的 INVOKEDYNAMIC,String owner :办法所在的类;String name :办法称号;String desc:办法描绘
MultiANewArrayInsnNode 用于 MULTIANEWARRAY 操作的字节码 String desc:类型描绘;int dims:维数
TableSwitchInsnNode 用于完成 TABLESWITCH 操作的字节码 int min:键的最小值;int max:键的最大值;LabelNode dflt:default 块对应的 LableList labels:对应的 Label 节点列表
TypeInsnNode 用于 NEW、ANEWARRAY 和 CHECKCAST 等类型操作 String desc:类型;VarInsnNode用于完成 ALOAD、ASTORE 等局部变量操作;int var:局部变量

上述的指令按特定次序存储在InsnList中。当办法履行时,InsnList中的指令集合也会按次序履行。

三 Tree API 实践

3.1、计算办法耗时

假设要对下面这个类的办法进行耗时计算:

//MethodTimeCostTestJava.java
public class MethodTimeCostTestJava {
    //static静态办法
    public static void staticTimeCostMonitor() throws InterruptedException {
        Thread.sleep(1000);
    }
    //非静态办法
    public void timeCostMonitor() throws InterruptedException {
        Thread.sleep(1000);
    }
}

完成

自定义ClassNode对办法进行插桩操作:

class AOutLibClassNode(val api: Int, private val classWriter: ClassWriter) : ClassNode(api) {
    private val mThresholdTime = 500
    companion object {
        const val owner = "org/ninetripods/lib_bytecode/common/TimeCostUtil"
        const val descripter = "Lorg/ninetripods/lib_bytecode/common/TimeCostUtil"
    }
    override fun visitEnd() {
        processTimeCost()
        //允许classWriter拜访ClassNode类中的信息
        accept(classWriter)
    }
    private fun processTimeCost(clzName: String? = "", methodName: String? = "", access: Int = 0) {
        for (methodNode: MethodNode in methods) {
            if (methodNode.name.equals("<init>") || methodNode.name.equals("<clinit>")) continue
            val instructions = methodNode.instructions
            //办法最初刺进
            val clzName = name
            val methodName = methodNode.name
            val access = methodNode.access
            instructions.insert(createMethodStartInstructions(clzName, methodName, access))
            //退出办法之前刺进
            methodNode.instructions.forEach { insnNode ->
                val opcode = insnNode.opcode
                if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {
                    val endInstructions = createMethodEndInstructions(clzName, methodName, access)
                    methodNode.instructions.insertBefore(insnNode, endInstructions)
                }
            }
        }
    }
    /**
     * 在method中创立入口指令集
     */
    private fun createMethodStartInstructions(
        clzName: String?,
        methodName: String?,
        access: Int,
    ): InsnList {
        val isStaticMethod = access and Opcodes.ACC_STATIC != 0
        return InsnList().apply {
            if (isStaticMethod) {
                add(FieldInsnNode(Opcodes.GETSTATIC, owner, "INSTANCE", descripter))
                //操作数栈中传入下面两个参数
                add(IntInsnNode(Opcodes.SIPUSH, mThresholdTime))
                add(LdcInsnNode("$clzName&$methodName"))
                add(
                    MethodInsnNode(
                        Opcodes.INVOKEVIRTUAL,
                        owner,
                        "recordStaticMethodStart",
                        "(ILjava/lang/String;)V",
                        false
                    )
                )
            } else {
                add(FieldInsnNode(Opcodes.GETSTATIC, owner, "INSTANCE", descripter))
                //操作数栈中传入对应的三个入参
                add(IntInsnNode(Opcodes.SIPUSH, mThresholdTime))
                add(LdcInsnNode("$clzName&$methodName"))
                add(VarInsnNode(Opcodes.ALOAD, 0))
                //将上面的三个参数传入下面的办法中
                add(
                    MethodInsnNode(
                        Opcodes.INVOKEVIRTUAL,
                        owner,
                        "recordMethodStart",
                        "(ILjava/lang/String;Ljava/lang/Object;)V",
                        false
                    )
                )
            }
        }
    }
    /**
     * 在method中退出时的指令集
     */
    private fun createMethodEndInstructions(
        clzName: String?,
        methodName: String?,
        access: Int,
    ): InsnList {
        val isStaticMethod = access and Opcodes.ACC_STATIC != 0
        return InsnList().apply {
            if (isStaticMethod) {
                add(FieldInsnNode(Opcodes.GETSTATIC, owner, "INSTANCE", descripter))
                //调用
                add(IntInsnNode(Opcodes.SIPUSH, mThresholdTime))
                add(LdcInsnNode("$clzName&$methodName"))
                add(
                    MethodInsnNode(
                        Opcodes.INVOKEVIRTUAL,
                        owner,
                        "recordStaticMethodEnd",
                        "(ILjava/lang/String;)V",
                        false
                    )
                )
            } else {
                add(FieldInsnNode(Opcodes.GETSTATIC, owner, "INSTANCE", descripter))
                //栈中传入对应的三个入参
                add(IntInsnNode(Opcodes.SIPUSH, mThresholdTime))
                add(LdcInsnNode("$clzName&$methodName"))
                add(VarInsnNode(Opcodes.ALOAD, 0))
                //将上面的三个参数传入下面的办法中
                add(
                    MethodInsnNode(
                        Opcodes.INVOKEVIRTUAL,
                        owner,
                        "recordMethodEnd",
                        "(ILjava/lang/String;Ljava/lang/Object;)V",
                        false
                    )
                )
            }
        }
    }
}

办法耗时处理TimeCostUtil类:

package org.ninetripods.lib_bytecode.common
/**
 * 全局办法耗时Util
 */
object TimeCostUtil {
    private const val TAG = "METHOD_COST"
    private val staticMethodObj by lazy { StaticMethodObject() }
    /**
     * 办法Map,其间key:办法名,value:耗时时刻
     */
    private val METHODS_MAP by lazy { ConcurrentHashMap<String, Long>() }
    /**
     * 目标办法
     * @param thresholdTime 阈值
     * @param methodName 办法名
     * @param clz 类名
     */
    fun recordMethodStart(thresholdTime: Int, methodName: String, clz: Any?) {
        try {
            METHODS_MAP[methodName] = System.currentTimeMillis()
        } catch (ex: Exception) {
            ex.printStackTrace()
        }
    }
    /**
     * 静态办法
     * @param thresholdTime 阈值
     * @param methodName 办法名
     */
    fun recordStaticMethodStart(thresholdTime: Int, methodName: String){
        recordMethodStart(thresholdTime, methodName, staticMethodObj)
    }
    /**
     * 目标办法
     * @param thresholdTime 阈值时刻
     * @param methodName 办法名
     * @param clz 类名
     */
    fun recordMethodEnd(thresholdTime: Int, methodName: String, clz: Any?) {
        Log.e(
            TAG,
            "\t methodName=>$methodName thresholdTime=>$thresholdTime method=>recordMethodEnd"
        )
        synchronized(TimeCostUtil::class.java) {
            try {
                if (METHODS_MAP.containsKey(methodName)) {
                    val startTime: Long = METHODS_MAP[methodName] ?: 0L
                    val costTime = System.currentTimeMillis() - startTime
                    METHODS_MAP.remove(methodName)
                    //办法耗时超过了阈值
                    if (costTime >= thresholdTime) {
                        val threadName = Thread.currentThread().name
                        Log.e(
                            TAG,
                            "\t methodName=>$methodName threadNam=>$threadName thresholdTime=>$thresholdTime costTime=>$costTime"
                        )
                    }
                }
            } catch (ex: Exception) {
                ex.printStackTrace()
            }
        }
    }
    /**
     * 静态办法
     * @param thresholdTime 阈值
     * @param methodName 办法名
     */
    fun recordStaticMethodEnd(thresholdTime: Int, methodName: String) {
        recordMethodEnd(thresholdTime, methodName, staticMethodObj)
    }
}
class StaticMethodObject 

然后是履行总入口:

public static void main(String[] args) {
    try {
        //运用示例
        ClassReader classReader = new ClassReader(MethodTimeCostTestJava.class.getName());
        //ClassWriter.COMPUTE_MAXS 主动计算帧栈信息(操作数栈 & 局部变量表)
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        //-------------------------办法1:ASM Tree API -------------------------
        ClassNode classNode = new AOutLibClassNode(Opcodes.ASM9, classWriter);
        //拜访者形式:将ClassVisitor传入ClassReader中,从而能够拜访ClassReader中的私有信息;相似一个接口回调。
        classReader.accept(classNode, ClassReader.EXPAND_FRAMES);
        FileUtil.INSTANCE.byte2File("lib_bytecode/files/MethodTimeCostTestJava.class",classWriter.toByteArray());
         } catch (IOException e) {
            e.printStackTrace();
        }
    }
//将终究修正的字节码写入指定路径
object FileUtil {
    fun byte2File(outputPath: String, sourceByte: ByteArray) {
        try {
            val file = File(outputPath)
            if (file.exists()) {
                file.delete()
            } else {
                file.parentFile.mkdir()
                file.createNewFile()
            }
            val inputStream = ByteArrayInputStream(sourceByte)
            val outputStream = FileOutputStream(file)
            val buffer = ByteArray(1024)
            var len = 0
            while (inputStream.read(buffer).apply { len = this } != -1) {
                outputStream.write(buffer, 0, len)
            }
            outputStream.flush()
            outputStream.close()
            inputStream.close()
        } catch (ex: Exception) {
            ex.printStackTrace()
        }
    }
}

履行成果:

public class MethodTimeCostTestJava {
    public MethodTimeCostTestJava() {
    }
    public static void staticTimeCostMonitor() throws InterruptedException {
        //1
        TimeCostUtil.INSTANCE.recordStaticMethodStart(500, "org/ninetripods/lib_bytecode/asm/demo/MethodTimeCostTestJava&staticTimeCostMonitor");
        Thread.sleep(1000L);
        //2
        TimeCostUtil.INSTANCE.recordStaticMethodEnd(500, "org/ninetripods/lib_bytecode/asm/demo/MethodTimeCostTestJava&staticTimeCostMonitor");
    }
    public void timeCostMonitor() throws InterruptedException {
        //3
        TimeCostUtil.INSTANCE.recordMethodStart(500, "org/ninetripods/lib_bytecode/asm/demo/MethodTimeCostTestJava&timeCostMonitor", this);
        Thread.sleep(1000L);
        //4
        TimeCostUtil.INSTANCE.recordMethodEnd(500, "org/ninetripods/lib_bytecode/asm/demo/MethodTimeCostTestJava&timeCostMonitor", this);
    }
}

能够看到终究生成类的办法中已经包含了咱们想要刺进的代码,包含静态办法和非静态办法。这儿仅仅简单的写个示例,其间AOutLibClassNode中的写法是参阅的 滴滴DoKit 中的代码。

四 参阅

【1】Tree API介绍
【2】ASM 修正字节码
【3】Java ASM详解:MethodVisitor和Opcode