一 ASM介绍
ASM
是一个通用的Java字节码操作和剖析框架
。它可用于修正现有类
或直接以二进制办法动态生成类
。ASM
提供了一些常见的字节码转换和剖析算法,能够根据这些算法构建定制的杂乱转换和代码剖析工具。
ASM
提供了与其他Java字节码框架
相似的功能,但更关注功能
。因为它的设计和完成尽或许的小和快
,它十分适合在动态系统中运用(但当然也能够以静态的办法运用,例如在编译器中)。关于ASM
更多介绍,能够拜见 ASM官网。
ASM
从组成结构上能够分红两部分,一部分为Core API
,另一部分为Tree API
。
-
Core API
包含asm.jar
、asm-util.jar
和asm-commons.jar
-
Tree API
包含asm-tree.jar
和asm-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
的内部类有:
首要用到的类有:
- ClassNode
- FieldNode
- MethodNode
- InsnList
- AbstractInsnNode
- TypeInsnNode
- VarInsnNode
- FieldInsnNode
- MethodInsnNode
- IincInsnNode
- InsnNode
- IntInsnNode
- InvokeDynamicInsnNode
- JumpInsnNode
- LabelNode
- LdcInsnNode
- LookupSwitchInsnNode
- MultiANewArrayInsnNode
- TableSwitchInsnNode
- TryCatchBlockNode
- InsnList
用类图表明为:
整理一下他们之间的关系:
-
ClassNode
包含FieldNode
和MethodNode
; -
MethodNode
中包含有序指令集合InsnList
及其反常处理TryCatchBlockNode
; -
InsnList
由很多个有序的单指令AbstractInsnNode
组合而成。 -
AbstractInsnNode
是一个抽象类,表明字节码指令的节点。一条指令最多只能在一个InsnList
中出现一次。AbstractInsnNode
的子类完成如下: 下面别离来看下每个类的意义。
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 class
。accept(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