当前位置:网站首页>Deeply explore the compilation and pile insertion technology (IV. ASM exploration)
Deeply explore the compilation and pile insertion technology (IV. ASM exploration)
2022-07-07 00:52:00 【Sharp surge】
Preface
Become an excellent Android Development , Need a complete Knowledge system , ad locum , Let's grow up as we think ~.
stay 《 Deeply explore the technology of compiling and inserting piles ( Two 、AspectJ)》 In this article, we have studied deeply AspectJ stay Android Use below . You can see AspectJ Very powerful , But it can only achieve 50% Bytecode operation scenario , If you want to achieve 100% Bytecode operation scenario , Then you have to use ASM.
Besides ,AspectJ Has a series of drawbacks : Because it's based on rules , So its entry point is relatively fixed , The operating freedom and development control of bytecode files are greatly reduced . also , He will generate some additional packaging code , Has some impact on performance and package size .
and ASM Basically any operation on bytecode can be realized , That is to say, the degree of freedom and development control is very high . It provides Visitor mode to access bytecode files , And only inject the code we want to inject .
ASM It originated from a doctoral research project , stay 2002 In open source , And from 5.x Version starts to support Java 8. also ,ASM It's a lot JVM Bytecode generation library specified by language , Its advantages in efficiency and performance are far superior to other bytecode operation libraries, such as javassist、AspectJ.
Mind map outline
Catalog
- One 、ASM Advantages and disadvantages
- 1、ASM The advantages of
- 2、ASM Against the trend of
- Two 、ASM Object model of (ASM Tree API)
- 1、 advantage
- 2、 shortcoming
- 3、 Access to the node
- 4、 Control operation code
- 3、 ... and 、ASM Event model for (ASM Core API)
- 2、 Class read ( analysis ) person ClassVisitor
- 3、 Summary
- Four 、 Comprehensive practical training
- 1、 Use ASM Bytecode Outline
- 2、 Use ASM Compiling the statistical method of pile insertion is time-consuming
- 3、 Replace all in the project globally new Thread
- 5、 ... and 、 summary
One 、ASM Advantages and disadvantages
Use ASM The advantages and disadvantages of operating bytecode Obviously , They are as follows .
1、ASM The advantages of
- 1)、 Memory usage is very small .
- 2)、 Very fast .
- 3)、 Flexible operation : The operation of bytecode is very flexible , You can insert 、 Delete 、 Modification and other operations .
- 4)、 There's a lot of room for imagination , It can be used to improve productivity .
- 5)、 Rich documentation and support from many communities .
2、ASM Against the trend of
It's hard to get started , Need to be right Java Have a good understanding of bytecode .
about ASM for , It provides Two models : Object model and event model .
below , Let's talk about it first ASM Object model of .
Two 、ASM Object model of (ASM Tree API)
Object model The essence It's a Encapsulated event model , it A tree view is used to describe a class , It contains multiple nodes , For example, method nodes 、 Field nodes and so on , And each node has child nodes , For example, there are sub nodes of operation code in the method section wait . Let's first understand the advantages and disadvantages of the object model implemented by this tree view mode .
1、 advantage
- 1)、 It is suitable for dealing with the modification of simple classes .
- 2)、 The cost of learning is lower .
- 3)、 Less code .
2、 shortcoming
- 1)、 Processing large amounts of information can complicate the code .
- 2)、 Code is hard to reuse .
Under the object model ASM Yes Two types of operational latitude , They are as follows :
- 1)、
Access to the node
: Get the specified class 、 Field 、 Method node . - 2)、
Control operation code ( For method nodes )
: Get opcode location 、 Replace 、 Delete 、 Insert the opcode 、 Output bytecode .
Now let's take a look at ASM These two types of operations .
3、 Access to the node
1)、 Get the node of the specified class
The code to get a class node is as follows :
ClassNode classNode = new ClassNode();
// 1
ClassReader classReader = new ClassReader(bytes);
// 2
classReader.accept(classNode, 0);
In the comments 1 It's about , Pass the byte array into a newly created ClassReader, At this time ASM Will use ClassReader To parse the bytecode . next , In the comments 2 It's about ,ClassReader After parsing the bytecode, you can pass accept Method to write the result to a ClassNode Among objects .
that , One ClassNode What information does it contain ?
As shown below :
Class node information
type | name | explain |
---|---|---|
int | version | class Of documents major edition ( Compilation of java edition ) |
int | access | Access level |
String | name | Class name , Use full address , Such as java/lang/String |
String | signature | Signature , Usually null |
String | superName | Parent class name , Use full address |
List | interfaces | Implemented interface , Use full address |
String | sourceFile | Source file , May be null |
String | sourceDebug | debug Source , May be null |
String | outerClass | External class |
String | outerMethod | External methods |
String | outerMethodDesc | External method description ( Including method parameters and return values ) |
List | visibleAnnotations | Visible annotations |
List | invisibleAnnotations | Invisible annotations |
List | attrs | Class Attribute |
List | innerClasses | Class's internal class list |
List | fields | Class |
List | methods | Class's method list |
2)、 Gets the node of the specified field
The code to get a field node is as follows :
for(FieldNode fieldNode : (List)classNode.fields) {
// 1
if(fieldNode.name.equals("password")) {
// 2
fieldNode.access = Opcodes.ACC_PUBLIC;
}
}
Field node list fields It's a ArrayList, It stores all the fields of the class node . In the comments 1 It's about , We traverse fields Set to find the target field node . next , In the comments 2 It's about , We set the access permission of the target field node to public.
besides , We can also add the required fields to the class , The code is as follows :
FieldNode fieldNode = new FieldNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "JsonChao", "I", null, null);
classNode.fields.add(fieldNode);
In the above code , We added a... Directly to the target class node "public static int JsonChao" Field of , It should be noted that , The third parameter is "I" It means int Type descriptor for .
that , For a field node , What field information does it contain ?
As shown below :
Field information
type | name | explain |
---|---|---|
int | access | Access level |
String | name | Field name |
String | signature | Signature , Usually null |
String | desc | Type description , for example Ljava/lang/String、D(double)、F(float) |
Object | value | Initial value , Usually it is null |
List | visibleAnnotations | Visible annotations |
List | invisibleAnnotations | Invisible annotations |
List | attrs | Field Attribute |
Next , Let's see how to get a method node .
3)、 Gets the specified method node
The code to get the specified method node is as follows :
for(MethodNode methodNode : (List)classNode.methods) {
// 1、 Determine whether the method name matches the target method
if(methodNode.name.equals("getName")) {
// 2、 To operate
}
}
methods Same as fields equally , Also a ArrayList, You can match the target method by traversing and judging the method name .
For a method node , It contains the following information :
The information contained in the method node
type | name | explain |
---|---|---|
int | access | Access level |
String | name | Method name |
String | desc | Methods described , It contains the return value and parameters of the method |
String | signature | Signature , Usually null |
List | exceptions | List of possible exceptions returned |
List | visibleAnnotations | List of visible annotations |
List | invisibleAnnotations | Invisible annotation list |
List | attrs | Methodical Attribute list |
Object | annotationDefault | Default annotation |
List[] | visibleParameterAnnotations | List of visible parameter annotations |
List[] | invisibleParameterAnnotations | Invisible Parameter annotation list |
InsnList | instructions | Opcode list |
List | tryCatchBlocks | try-catch Block list |
int | maxStack | Maximum operation stack depth |
int | maxLocals | The size of the maximum local variable area |
List | localVariables | Local ( Local ) Variable node list |
4、 Control operation code
Before manipulating bytecode , We must first understand instructions
, namely Opcode list , It is Method node where the opcode is stored , among Each element represents a line of opcode .
ASM Encapsulate a line of bytecode into a xxxInsnNode(Insn It means Instruction Abbreviation , Immediate instruction / opcode ), for example ALOAD/ARestore Instructions are encapsulated in variable opcode nodes VarInsnNode,INVOKEVIRTUAL Instructions are encapsulated in the method opcode node MethodInsnNode In .
For all instruction nodes xxxInsnNode Come on , They all inherit from the abstract opcode node AbstractInsnNode
. The usage details of all its derived classes are as follows .
Description of all script nodes
name | explain | Parameters |
---|---|---|
FieldInsnNode | be used for GETFIELD and PUTFIELD Bytecode for field operations such as | String owner The class of the field String name Name of field String desc Type of field |
FrameNode | The corresponding frame node of the stack mapping frame | To be added |
IincInsnNode | be used for IINC Bytecode of variable self addition operation | int var: The location of the target local variable int incr: The number to be added |
InsnNode | Bytecode for all nonparametric operations , for example ALOAD_0,DUP( Note that does not include POP) | nothing |
IntInsnNode | be used for BIPUSH、SIPUSH and NEWARRAY These three direct operations on integers | int operand: Integer value of the operation |
InvokeDynamicInsnNode | be used for Java7 Newly added INVOKEDYNAMIC Bytecode of the operation | String name: Method name String desc: Methods described Handle bsm: Handle Object[] bsmArgs: Parameter constants |
JumpInsnNode | be used for IFEQ or GOTO Wait for jump operation bytecode | LabelNode lable: The goal is lable |
LabelNode | A that represents a jump point Label node | nothing |
LdcInsnNode | Use LDC Bytecode that loads the reference value in the constant pool and inserts it | Object cst: Reference value |
LineNumberNode | Node representing the line number | int line: Line number LabelNode start: The corresponding first one Label |
LookupSwitchInsnNode | Used to implement LOOKUPSWITCH Bytecode of the operation | LabelNode dflt:default Block corresponds to Lable List keys Key list List labels: Corresponding Label The node list |
MethodInsnNode | be used for INVOKEVIRTUAL Bytecode of traditional method call operation , Do not apply to Java7 Newly added INVOKEDYNAMIC | String owner : The class of the method String name : Method name String desc: Methods described |
MultiANewArrayInsnNode | be used for MULTIANEWARRAY Bytecode of the operation | String desc: Type description int dims: dimension |
TableSwitchInsnNode | Used to implement TABLESWITCH Bytecode of the operation | int min: The minimum value of the key int max: The maximum value of the key LabelNode dflt:default Block corresponds to Lable List labels: Corresponding Label The node list |
TypeInsnNode | Used to implement NEW、ANEWARRAY and CHECKCAST Bytecode of type related operations | String desc: type |
VarInsnNode | Used to implement ALOAD、ASTORE Bytecode of local variable operation | int var: local variable |
below , Let's begin by explaining the common ways of bytecode manipulation .
1、 Get the location of the opcode
The code to get the location of the specified opcode is as follows :
for(AbstractInsnNode ainNode : methodNode.instructions.toArray()) {
if(ainNode.getOpcode() == Opcodes.SIPUSH && ((IntInsnNode)ainNode).operand == 16) {
....// To operate
}
}
In general, we can't determine the specific position of the opcode in the list , therefore The key features are usually determined by traversal , To locate the specified opcode , The above code can locate a SIPUSH 16 Bytecode , It should be noted that , Sometimes there are multiple identical instructions in a method , This is that we need to identify its characteristics by judging the front and rear byte codes , You can also record the number of hits and set it to operate at a certain time , Generally, we use the second .
2、 Replace the specified opcode
The code to replace the specified opcode is as follows :
for(AbstractInsnNode ainNode : methodNode.instructions.toArray()) {
if(ainNode.getOpcode() == Opcodes.BIPUSH && ((IntInsnNode)ainNode).operand == 16) {
methodNode.instructions.set(ainNode, new VarInsnNode(Opcodes.ALOAD, 1));
}
}
Here we are Call directly InsnList Of set Method can replace the specified opcode object , We're getting "BIPUSH 64" After the position of bytecode , Replace the opcode that encapsulates it with a new VarInsnNode opcode , This new opcode encapsulates "ALOAD 1" Bytecode , In the original program Set the value to 16
Replace with Set the value as a local variable 1
.
3、 Delete the specified opcode
methodNode.instructions.remove(xxx);
xxx Represents the opcode instance to be deleted , We call directly with InsnList Of remove Method to remove it .
4、 Insert the specified opcode
InsnList Mainly provides Four types of Method is used to insert bytecode , As shown below :
- 1)、
add(AbstractInsnNode insn)
: Add an opcode to InsnList At the end of . - 2)、
insert(AbstractInsnNode insn)
: Insert an opcode into this InsnList The beginning of . - 3)、
insert(AbstractInsnNode insnNode,AbstractInsnNode insn)
: Insert an opcode under another opcode . - 4)、
insertBefore(AbstractInsnNode insnNode,AbstractInsnNode insn)
Insert one opcode above another
Next, let's look at how to use these methods to insert the specified opcode , The code is as follows :
for(AbstractInsnNode ainNode : methodNode.instructions.toArray()) {
if(ainNode.getOpcode() == Opcodes.BIPUSH && ((IntInsnNode)ainNode).operand == 16) {
methodNode.instructions.insert(ainNode, new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/awt/image/BufferedImage", "getWidth", "(Ljava/awt/image/ImageObserver;)I"));
methodNode.instructions.insert(ainNode, new InsnNode(Opcodes.ACONSTNULL));
methodNode.instructions.set(ainNode, new VarInsnNode(Opcodes.ALOAD, 1));
}
}
such , We can
BIPUSH 16
```java
Replace with
```java
ALOAD 1
ACONSTNULL
INVOKEVIRTUAL java/awt/image/BufferedImage.getWidth(Ljava/awt/image/ImageObserver;)I
```java
** After we manipulate the specified class node , You can use ASM Of ClassWriter Class to output bytecode **, The code is as follows :
```java
// 1、 Give Way ClassWriter Calculate the maximum stack depth and stack mapping frame by yourself
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTEFRAMES);
classNode.accept(classWriter);
return classWriter.toByteArray();
About ClassWriter Specific usage of , We will be in ASM Core API This part will explain step by step . below , Let's have a look at ASM Event model for .
3、 ... and 、ASM Event model for (ASM Core API)
The object model is encapsulated by the event model , Therefore, it will be more difficult to get started with the event model .
For the event model , it The visitor pattern in the design pattern is adopted . Its emergence is to better solve such a demand : Yes A Elements and N Species algorithm , Each algorithm can act on any element , And there are different ways to run on different elements .
Before visitor mode appears , We usually add... To the class corresponding to each element N A way , Then implement an algorithm in each method , however , This approach can easily lead to excessive code coupling , And poor maintainability .
therefore , Visitor pattern came into being , We can establish N Visitors , And each visitor has an algorithm and its internal A Operation mode . When we need to call an algorithm , Let the corresponding visitor access the element , Then let the visitor select the corresponding algorithm according to the visited object .
It should be noted that , Visitors do not directly manipulate elements , Instead, let the element class call accept Method to receive visitors , then , The visitor starts calling... In the inner method of the element class visit Method to access the current element class . such , Visitors can directly access the internal private members in the element class , The advantage is Avoid exposing unnecessary internal details .
To understand ASM Event model for , We need to understand the How two important members work Have a deeper understanding . They are Class visitor ClassVisitor And Class read ( analysis ) person ClassReader.
From the bytecode perspective , One Java Classes are composed of many components , And this includes superclasses 、 Interface 、 attribute 、 Fields and methods, etc . When we are using ASM When handling , They can be regarded as corresponding events . therefore ASM Provides a Class visitor ClassVisitor, To access the components of the current class , When the parser ClassReader When you encounter each of the above components in turn ,ClassVisitor Corresponding to visitor Event handler methods are called one by one .
Similar to the class , The method is also composed of multiple components , It corresponds to the method property 、 Comments and compiled code (Class Bytecode ).ASM Of MethodVisitor Provides a hook( hook ) Mechanism , So that you can access every opcode in the method , In this way, we can make fine-grained modifications to the bytecode file .
below , Let's analyze them one by one .
1、 Class visitor ClassVisitor
Usually we use ASM The visitor pattern has a template code , As shown below :
InputStream is = new FileInputStream(classFile);
// 1
ClassReader classReader = new ClassReader(is);
// 2
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
// 3
ClassVisitor classVisitor = new TraceClassAdapter(Opcodes.ASM5, classWriter);
// 4
classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
First , In the comments 1 It's about , We Convert the target file to stream form , And integrate it into the class reader ClassReader In . then , In the comments 2 It's about , We Build a class writer ClassWriter, Its parameters COMPUTE_MAXS The function of is to entrust the task of automatically calculating the maximum value of the local variable table and the maximum value of the operand stack to ASM. next , In the comments 3 It's about , A new custom class accessor , This custom ClassVisitor
Its purpose is to Insert the corresponding timing code at the beginning and end of each method , In order to count the time consumption of each method . Last , In the comments 4 It's about , Class reader ClassReader The instance called by the visitor has its own accept Method receives a classVisitor example , It should be noted that , The second parameter specifies EXPAND_FRAMES
, The purpose is to explain in Read class You need to expand the stack mapping frame at the same time (StackMap Frame), If we need to use custom MethodVisitor This parameter must be specified when modifying the instruction in the method ,.
above , We talked about stack mapping frames (StackMap Frame), What exactly is it ?
Stack mapping frame StackMap Frame
It is Java 6 A verification mechanism introduced later , be used for test Java Correctness of bytecode . The way it works is Record the theoretical state of the operand stack in the method after each key step is completed , then , In actual operation ,ASM Will compare its actual state with its theoretical state , If the status is inconsistent, an error has occurred .
But the implementation of stack mapping frame is not simple , So by calling classReader Example of accept Method we can make ASM Automatically calculate stack mapping frame , Although this Will increase 50% Additional operations . Besides , There may be a small probability that Stack mapping frame validation failed The situation of , for example :VerifyError: Inconsistent stackmap frames at branch target
This mistake .
The most common reason may be due to Caused by a bytecode error , here , We should check the corresponding bytecode implementation code . Besides , It could be JDK Version support problem or ASM My own flaws , however , This rarely happens .
2、 Class read ( analysis ) person ClassVisitor
Now? , Let's go back to the above note 4 Code at , ad locum , We call classReader Of accept Method receives a visitor classVisitor, below , Let's look at its internal implementation , The code is as follows ( The source code implementation is long , Here we just need to focus on the code at the comments :
/**
* Makes the given visitor visit the Java class of this {@link ClassReader}
* . This class is the one specified in the constructor (see
* {@link #ClassReader(byte[]) ClassReader}).
*
* @param classVisitor
* the visitor that must visit this class.
* @param flags
* option flags that can be used to modify the default behavior
* of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}
* , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}.
*/
public void accept(final ClassVisitor classVisitor, final int flags) {
accept(classVisitor, new Attribute[0], flags);
}
stay accept Method continues to call classReader Another accept Overloading methods , As shown below :
public void accept(final ClassVisitor classVisitor,
final Attribute[] attrs, final int flags) {
int u = header; // current offset in the class file
char[] c = new char[maxStringLength]; // buffer used to read strings
Context context = new Context();
context.attrs = attrs;
context.flags = flags;
context.buffer = c;
// 1、 Read the description of the class , for example access、name wait
int access = readUnsignedShort(u);
String name = readClass(u + 2, c);
String superClass = readClass(u + 4, c);
String[] interfaces = new String[readUnsignedShort(u + 6)];
u += 8;
for (int i = 0; i < interfaces.length; ++i) {
interfaces[i] = readClass(u, c);
u += 2;
}
// 2、 Read the attribute information of the class , For example, signature signature、sourceFile wait .
String signature = null;
String sourceFile = null;
String sourceDebug = null;
String enclosingOwner = null;
String enclosingName = null;
String enclosingDesc = null;
int anns = 0;
int ianns = 0;
int tanns = 0;
int itanns = 0;
int innerClasses = 0;
Attribute attributes = null;
u = getAttributes();
for (int i = readUnsignedShort(u); i > 0; --i) {
String attrName = readUTF8(u + 2, c);
// tests are sorted in decreasing frequency order
// (based on frequencies observed on typical classes)
if ("SourceFile".equals(attrName)) {
sourceFile = readUTF8(u + 8, c);
} else if ("InnerClasses".equals(attrName)) {
innerClasses = u + 8;
} else if ("EnclosingMethod".equals(attrName)) {
enclosingOwner = readClass(u + 8, c);
int item = readUnsignedShort(u + 10);
if (item != 0) {
enclosingName = readUTF8(items[item], c);
enclosingDesc = readUTF8(items[item] + 2, c);
}
} else if (SIGNATURES && "Signature".equals(attrName)) {
signature = readUTF8(u + 8, c);
} else if (ANNOTATIONS
&& "RuntimeVisibleAnnotations".equals(attrName)) {
anns = u + 8;
} else if (ANNOTATIONS
&& "RuntimeVisibleTypeAnnotations".equals(attrName)) {
tanns = u + 8;
} else if ("Deprecated".equals(attrName)) {
access |= Opcodes.ACC_DEPRECATED;
} else if ("Synthetic".equals(attrName)) {
access |= Opcodes.ACC_SYNTHETIC
| ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
} else if ("SourceDebugExtension".equals(attrName)) {
int len = readInt(u + 4);
sourceDebug = readUTF(u + 8, len, new char[len]);
} else if (ANNOTATIONS
&& "RuntimeInvisibleAnnotations".equals(attrName)) {
ianns = u + 8;
} else if (ANNOTATIONS
&& "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
itanns = u + 8;
} else if ("BootstrapMethods".equals(attrName)) {
int[] bootstrapMethods = new int[readUnsignedShort(u + 8)];
for (int j = 0, v = u + 10; j < bootstrapMethods.length; j++) {
bootstrapMethods[j] = v;
v += 2 + readUnsignedShort(v + 2) << 1;
}
context.bootstrapMethods = bootstrapMethods;
} else {
Attribute attr = readAttribute(attrs, attrName, u + 8,
readInt(u + 4), c, -1, null);
if (attr != null) {
attr.next = attributes;
attributes = attr;
}
}
u += 6 + readInt(u + 4);
}
// 3、 Access the description of the class
classVisitor.visit(readInt(items[1] - 7), access, name, signature,
superClass, interfaces);
// 4、 Access source code and debug Information
if ((flags & SKIP_DEBUG) == 0
&& (sourceFile != null || sourceDebug != null)) {
classVisitor.visitSource(sourceFile, sourceDebug);
}
// 5、 Access external classes
if (enclosingOwner != null) {
classVisitor.visitOuterClass(enclosingOwner, enclosingName,
enclosingDesc);
}
// 6、 Access class annotations and type annotations
if (ANNOTATIONS && anns != 0) {
for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
classVisitor.visitAnnotation(readUTF8(v, c), true));
}
}
if (ANNOTATIONS && ianns != 0) {
for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
classVisitor.visitAnnotation(readUTF8(v, c), false));
}
}
if (ANNOTATIONS && tanns != 0) {
for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
v = readAnnotationTarget(context, v);
v = readAnnotationValues(v + 2, c, true,
classVisitor.visitTypeAnnotation(context.typeRef,
context.typePath, readUTF8(v, c), true));
}
}
if (ANNOTATIONS && itanns != 0) {
for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
v = readAnnotationTarget(context, v);
v = readAnnotationValues(v + 2, c, true,
classVisitor.visitTypeAnnotation(context.typeRef,
context.typePath, readUTF8(v, c), false));
}
}
// 7、 Access the properties of a class
while (attributes != null) {
Attribute attr = attributes.next;
attributes.next = null;
classVisitor.visitAttribute(attributes);
attributes = attr;
}
// 8、 Access inner classes
if (innerClasses != 0) {
int v = innerClasses + 2;
for (int i = readUnsignedShort(innerClasses); i > 0; --i) {
classVisitor.visitInnerClass(readClass(v, c),
readClass(v + 2, c), readUTF8(v + 4, c),
readUnsignedShort(v + 6));
v += 8;
}
}
// 9、 Access fields and methods
u = header + 10 + 2 * interfaces.length;
for (int i = readUnsignedShort(u - 2); i > 0; --i) {
u = readField(classVisitor, context, u);
}
u += 2;
for (int i = readUnsignedShort(u - 2); i > 0; --i) {
u = readMethod(classVisitor, context, u);
}
// Call... At the end of accessing the current class
classVisitor.visitEnd();
}
First , stay classReader Example of accept Comment in method 1 And comments 2 It's about , We will Start with class related bytecode parsing : Read the class description and attribute information . next , In the comments 3 ~ notes 8 It's about , We Called classVisitor A series of visitxxx Method access classReader The information saved in memory after parsing the bytecode . then , In the comments 9 It's about , Respectively called readField Methods and readMethod Method to access the methods and fields in the class . Last , call classVisitor Of visitEnd Identity access ended .
1)、 Parsing of fields in class
here , Let's take a look first readField
Source code implementation , As shown below :
/**
* Reads a field and makes the given visitor visit it.
*
* @param classVisitor
* the visitor that must visit the field.
* @param context
* information about the class being parsed.
* @param u
* the start offset of the field in the class file.
* @return the offset of the first byte following the field in the class.
*/
private int readField(final ClassVisitor classVisitor,
final Context context, int u) {
// 1、 Read the description of the field
char[] c = context.buffer;
int access = readUnsignedShort(u);
String name = readUTF8(u + 2, c);
String desc = readUTF8(u + 4, c);
u += 6;
// 2、 Read the properties of the field
String signature = null;
int anns = 0;
int ianns = 0;
int tanns = 0;
int itanns = 0;
Object value = null;
Attribute attributes = null;
for (int i = readUnsignedShort(u); i > 0; --i) {
String attrName = readUTF8(u + 2, c);
// tests are sorted in decreasing frequency order
// (based on frequencies observed on typical classes)
if ("ConstantValue".equals(attrName)) {
int item = readUnsignedShort(u + 8);
value = item == 0 ? null : readConst(item, c);
} else if (SIGNATURES && "Signature".equals(attrName)) {
signature = readUTF8(u + 8, c);
} else if ("Deprecated".equals(attrName)) {
access |= Opcodes.ACC_DEPRECATED;
} else if ("Synthetic".equals(attrName)) {
access |= Opcodes.ACC_SYNTHETIC
| ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
} else if (ANNOTATIONS
&& "RuntimeVisibleAnnotations".equals(attrName)) {
anns = u + 8;
} else if (ANNOTATIONS
&& "RuntimeVisibleTypeAnnotations".equals(attrName)) {
tanns = u + 8;
} else if (ANNOTATIONS
&& "RuntimeInvisibleAnnotations".equals(attrName)) {
ianns = u + 8;
} else if (ANNOTATIONS
&& "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
itanns = u + 8;
} else {
Attribute attr = readAttribute(context.attrs, attrName, u + 8,
readInt(u + 4), c, -1, null);
if (attr != null) {
attr.next = attributes;
attributes = attr;
}
}
u += 6 + readInt(u + 4);
}
u += 2;
// 3、 Declaration of access field
FieldVisitor fv = classVisitor.visitField(access, name, desc,
signature, value);
if (fv == null) {
return u;
}
// 4、 Access field annotations and type annotations
if (ANNOTATIONS && anns != 0) {
for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
fv.visitAnnotation(readUTF8(v, c), true));
}
}
if (ANNOTATIONS && ianns != 0) {
for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
fv.visitAnnotation(readUTF8(v, c), false));
}
}
if (ANNOTATIONS && tanns != 0) {
for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
v = readAnnotationTarget(context, v);
v = readAnnotationValues(v + 2, c, true,
fv.visitTypeAnnotation(context.typeRef,
context.typePath, readUTF8(v, c), true));
}
}
if (ANNOTATIONS && itanns != 0) {
for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
v = readAnnotationTarget(context, v);
v = readAnnotationValues(v + 2, c, true,
fv.visitTypeAnnotation(context.typeRef,
context.typePath, readUTF8(v, c), false));
}
}
// 5、 Access the properties of the field
while (attributes != null) {
Attribute attr = attributes.next;
attributes.next = null;
fv.visitAttribute(attributes);
attributes = attr;
}
// Call... At the end of the access field
fv.visitEnd();
return u;
}
Similar to reading such information , First , In the comments 1 And comments 2 It's about , Meeting Start the bytecode analysis related to the field : Read the description and attribute information of the field . then , In the comments 3 ~ notes 5 It's about The descriptions of the fields are accessed sequentially 、 annotation 、 Type annotation and its attribute information . Last , Called FieldVisitor Example of visitEnd Method ends the access to the field information .
2)、 Analysis of methods in class
below , We're looking readMethod
Implementation code , As shown below :
/**
* Reads a method and makes the given visitor visit it.
*
* @param classVisitor
* the visitor that must visit the method.
* @param context
* information about the class being parsed.
* @param u
* the start offset of the method in the class file.
* @return the offset of the first byte following the method in the class.
*/
private int readMethod(final ClassVisitor classVisitor,
final Context context, int u) {
// 1、 Read method description information
char[] c = context.buffer;
context.access = readUnsignedShort(u);
context.name = readUTF8(u + 2, c);
context.desc = readUTF8(u + 4, c);
u += 6;
// 2、 Read method attribute information
int code = 0;
int exception = 0;
String[] exceptions = null;
String signature = null;
int methodParameters = 0;
int anns = 0;
int ianns = 0;
int tanns = 0;
int itanns = 0;
int dann = 0;
int mpanns = 0;
int impanns = 0;
int firstAttribute = u;
Attribute attributes = null;
for (int i = readUnsignedShort(u); i > 0; --i) {
String attrName = readUTF8(u + 2, c);
// tests are sorted in decreasing frequency order
// (based on frequencies observed on typical classes)
if ("Code".equals(attrName)) {
if ((context.flags & SKIP_CODE) == 0) {
code = u + 8;
}
} else if ("Exceptions".equals(attrName)) {
exceptions = new String[readUnsignedShort(u + 8)];
exception = u + 10;
for (int j = 0; j < exceptions.length; ++j) {
exceptions[j] = readClass(exception, c);
exception += 2;
}
} else if (SIGNATURES && "Signature".equals(attrName)) {
signature = readUTF8(u + 8, c);
} else if ("Deprecated".equals(attrName)) {
context.access |= Opcodes.ACC_DEPRECATED;
} else if (ANNOTATIONS
&& "RuntimeVisibleAnnotations".equals(attrName)) {
anns = u + 8;
} else if (ANNOTATIONS
&& "RuntimeVisibleTypeAnnotations".equals(attrName)) {
tanns = u + 8;
} else if (ANNOTATIONS && "AnnotationDefault".equals(attrName)) {
dann = u + 8;
} else if ("Synthetic".equals(attrName)) {
context.access |= Opcodes.ACC_SYNTHETIC
| ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
} else if (ANNOTATIONS
&& "RuntimeInvisibleAnnotations".equals(attrName)) {
ianns = u + 8;
} else if (ANNOTATIONS
&& "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
itanns = u + 8;
} else if (ANNOTATIONS
&& "RuntimeVisibleParameterAnnotations".equals(attrName)) {
mpanns = u + 8;
} else if (ANNOTATIONS
&& "RuntimeInvisibleParameterAnnotations".equals(attrName)) {
impanns = u + 8;
} else if ("MethodParameters".equals(attrName)) {
methodParameters = u + 8;
} else {
Attribute attr = readAttribute(context.attrs, attrName, u + 8,
readInt(u + 4), c, -1, null);
if (attr != null) {
attr.next = attributes;
attributes = attr;
}
}
u += 6 + readInt(u + 4);
}
u += 2;
// 3、 Access method description information
MethodVisitor mv = classVisitor.visitMethod(context.access,
context.name, context.desc, signature, exceptions);
if (mv == null) {
return u;
}
/*
* if the returned MethodVisitor is in fact a MethodWriter, it means
* there is no method adapter between the reader and the writer. If, in
* addition, the writers constant pool was copied from this reader
* (mw.cw.cr == this), and the signature and exceptions of the method
* have not been changed, then it is possible to skip all visit events
* and just copy the original code of the method to the writer (the
* access, name and descriptor can have been changed, this is not
* important since they are not copied as is from the reader).
*/
if (WRITER && mv instanceof MethodWriter) {
MethodWriter mw = (MethodWriter) mv;
if (mw.cw.cr == this && signature == mw.signature) {
boolean sameExceptions = false;
if (exceptions == null) {
sameExceptions = mw.exceptionCount == 0;
} else if (exceptions.length == mw.exceptionCount) {
sameExceptions = true;
for (int j = exceptions.length - 1; j >= 0; --j) {
exception -= 2;
if (mw.exceptions[j] != readUnsignedShort(exception)) {
sameExceptions = false;
break;
}
}
}
if (sameExceptions) {
/*
* we do not copy directly the code into MethodWriter to
* save a byte array copy operation. The real copy will be
* done in ClassWriter.toByteArray().
*/
mw.classReaderOffset = firstAttribute;
mw.classReaderLength = u - firstAttribute;
return u;
}
}
}
// 4、 Access method parameter information
if (methodParameters != 0) {
for (int i = b[methodParameters] & 0xFF, v = methodParameters + 1; i > 0; --i, v = v + 4) {
mv.visitParameter(readUTF8(v, c), readUnsignedShort(v + 2));
}
}
// 5、 Annotation information of access method
if (ANNOTATIONS && dann != 0) {
AnnotationVisitor dv = mv.visitAnnotationDefault();
readAnnotationValue(dann, c, null, dv);
if (dv != null) {
dv.visitEnd();
}
}
if (ANNOTATIONS && anns != 0) {
for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
mv.visitAnnotation(readUTF8(v, c), true));
}
}
if (ANNOTATIONS && ianns != 0) {
for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
mv.visitAnnotation(readUTF8(v, c), false));
}
}
if (ANNOTATIONS && tanns != 0) {
for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
v = readAnnotationTarget(context, v);
v = readAnnotationValues(v + 2, c, true,
mv.visitTypeAnnotation(context.typeRef,
context.typePath, readUTF8(v, c), true));
}
}
if (ANNOTATIONS && itanns != 0) {
for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
v = readAnnotationTarget(context, v);
v = readAnnotationValues(v + 2, c, true,
mv.visitTypeAnnotation(context.typeRef,
context.typePath, readUTF8(v, c), false));
}
}
if (ANNOTATIONS && mpanns != 0) {
readParameterAnnotations(mv, context, mpanns, true);
}
if (ANNOTATIONS && impanns != 0) {
readParameterAnnotations(mv, context, impanns, false);
}
// 6、 Access the property information of the method
while (attributes != null) {
Attribute attr = attributes.next;
attributes.next = null;
mv.visitAttribute(attributes);
attributes = attr;
}
// 7、 Access the bytecode information corresponding to the method code
if (code != 0) {
mv.visitCode();
readCode(mv, context, code);
}
// 8、visits the end of the method
mv.visitEnd();
return u;
}
Reading of similar and fields 、 The interview routine is the same , First , In the comments 1 And comments 2 It's about , Meeting Start with bytecode parsing related to the method : Read the method description and attribute information . then , In the comments 3 ~ notes 7 It's about The method descriptions are accessed sequentially 、 Parameters 、 annotation 、 attribute 、 Bytecode information corresponding to the method code . It should be noted that , stay readCode In the method , It also reads the bytecode information of the internal code of the method first , For example, head 、 Properties and so on , then , The corresponding instruction set will be accessed . Last , In the comments 8 It's about Called MethodVisitor Example of visitEnd Method ends the access to method information .
From the above to ClassVisitor And ClassReader My analysis seems ,ClassVisitor Defined as one that can receive and parse ClassReader Class of incoming information . When in accpet In the method ClassVisitor visit ClassReader when ,ClassReader The bytecode will be parsed first , And keep the results stored in memory through various calls visitxxx Method is passed into ClassVisitor In .
It should be noted that , among Only visit This method must be called once , Because it Get the description information of the class header , Obviously easy to see , It's essential , For others visitxxx I'm not sure about the method . For example, one of them visitMethod Method , Only when ClassReader When parsing the bytecode of a method , Only once visitMethod Method , And thus generate a method visitor MethodVisitor Example .
then , This MethodVisitor An example of ClassVisitor Start accessing the property information of the current method , about ClassVisitor Come on , It only deals with things related to classes , And the method is outsourced to MethodVisitor To deal with . This is a great advantage for visitors : Separate the responsibility of accessing a complex thing through different types of interrelated visitors .
From the above we can see , The object model is an encapsulation of the event model . Among them ClassNode In fact, that is ClassVisitor A subclass of , It will be responsible for ClassReader The incoming information is classified and stored . Again ,MethodNode It's also MethodVisitor A subclass of , It will be responsible for ClassReader Connect the incoming opcode instruction information into a list and save it .
and ClassWriter It's also ClassVisitor A subclass of , however , It doesn't store information , Instead, the incoming information is immediately translated into bytecode , And output them at any time after . about ClassReader For the interviewee , It is responsible for reading the byte stream data in our incoming class file , And provide the operation of parsing all class attribute information contained in the flow .
Last , In order to further explain what we have explained above ClassReader And ClassVisitor Our working mechanism is more visualized , Borrow here hakugyokurou A flowchart for reviewing and combing , As shown below :
Be careful : the second " Instantiation , By constructor ..." Need to get rid of it.
3、 Summary
ASM Core API Similar to parsing XML In the document SAX The way , Directly stream bytecode files , Instead of reading the entire structure of this class into memory . The advantage is to save memory as much as possible , The difficulty is that programming requires a certain degree of JVM Bytecode basis . Because of its good performance , So usually we will directly use Core API. below , Let's review In event model Core API Key components of , As shown below :
- 1)、
ClassReader
: For reading compiled .class file . - 2)、
ClassWriter
: For rebuilding compiled classes , Such as modifying the class name 、 Properties and methods , Can also generate new class bytecode file . - 3)、
Various Visitor class
: As mentioned above ,Core API Process from top to bottom according to bytecode , For different areas of bytecode files, there are different Visitor, For example, for accessing methods MethodVisitor、 For accessing class variables FieldVisitor、 For accessing annotations AnnotationVisitor wait . In order to achieve AOP, The key point is to use MethodVisitor.
Four 、 Comprehensive practical training
At the beginning of use ASM Core API Before , We need to know ASM Bytecode Outline
Tool use .
1、 Use ASM Bytecode Outline
When we use ASM When writing bytecode , Usually write a series visitXXXXInsn()
Method to write the corresponding mnemonic , therefore You need to convert each line of source code into the corresponding mnemonic , And then through ASM The syntax of is converted to the corresponding visitXXXXInsn(). In order to solve this cumbersome and time-consuming process , therefore ,ASM Bytecode Outline It came into being .
First , We need to install ASM Bytecode Outline gradle plug-in unit , After installation , We can Right click directly in the target class and select... In the bottom area of the drop-down box Show Bytecode outline, then ,AS The bytecode corresponding to the target class and ASM Information viewing area . We directly Choose... From the new tab ASMified This tab You can see the corresponding ASM Code , As shown in the figure below :
In order to better understand the above knowledge in practice , We can Use ASM The time-consuming statistics of pile insertion implementation method and Replace all... In the project new Thread. It is directly given here Android Development of master courses ASM Actual project address .
Be careful : If you currently need to use ASM Outline Class refers to other classes in the project .java file , You need to use it first ASM Outline Compile it into .class file .
2、 Use ASM Compiling the statistical method of pile insertion is time-consuming
Use ASM The time-consuming method of compiling and inserting pile statistics can be divided into the following three steps :
- 1)、 First , We need to customize gradle plugin To interfere with the compilation process .
- 2)、 then , Get all the class Document and jar package , And then traverse them .
- 3)、 Last , utilize ASM To modify bytecode , To achieve the purpose of inserting piles .
At the beginning , We can do it in Application Of onCreate Method First write down the code to be inserted , As shown below :
@Override
public void onCreate() {
long startTime = System.currentTimeMillis();
super.onCreate();
long endTime = System.currentTimeMillis() - startTime;
StringBuilder sb = new StringBuilder();
sb.append("com/sample/asm/SampleApplication.onCreate time: ");
sb.append(endTime);
Log.d("MethodCostTime", sb.toString());
}
This is convenient Then you can use ASM Bytecode Outline Of ASMified Under the Show differences To show the code differences between two adjacent modifications , After its modification ASM The code comparison diagram is shown below :
The difference code shown in the right figure is what we need to add ASM Code . Here we use it directly ASM The pattern of events , namely ASM Of Core API To read and modify bytecode , The code is as follows :
ClassReader classReader = new ClassReader(is);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
// 1
ClassVisitor classVisitor = new TraceClassAdapter(Opcodes.ASM5, classWriter);
classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
We have analyzed the above implementation code in detail , When classReader call accept Method, the class file will be read and deleted classVisitor visit . that , How do we operate on the bytecode in the method ?
In the comments 1 It's about , We Customized a ClassVisitor, The mystery lies in it , The implementation code is as follows :
public static class TraceClassAdapter extends ClassVisitor {
private String className;
TraceClassAdapter(int i, ClassVisitor classVisitor) {
super(i, classVisitor);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
this.className = name;
}
@Override
public void visitInnerClass(final String s, final String s1, final String s2, final int i) {
super.visitInnerClass(s, s1, s2, i);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
return new TraceMethodAdapter(api, methodVisitor, access, name, desc, this.className);
}
@Override
public void visitEnd() {
super.visitEnd();
}
}
Because we only need to operate on the bytecode of the method , Deal directly with visitMethod
This method can . Here, we will directly class observers ClassVisitor From the interview MethodVisitor It was packaged , Customized AdviceAdapter The way to achieve , and AdviceAdapter It's also MethodVisitor Subclasses of , differ MethodVisitor Yes. , It itself provides onMethodEnter And onMethodExit Method , It is very convenient for us to implement the pre and post insertion of the method . The implementation code is as follows :
private int timeLocalIndex = 0;
@Override
protected void onMethodEnter() {
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
// 1
timeLocalIndex = newLocal(Type.LONG_TYPE);
mv.visitVarInsn(LSTORE, timeLocalIndex);
}
@Override
protected void onMethodExit(int opcode) {
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LLOAD, timeLocalIndex);
// The value here is at the top of the stack
mv.visitInsn(LSUB);
// Because this value will be used later, save it to the local variable table first
mv.visitVarInsn(LSTORE, timeLocalIndex);
int stringBuilderIndex = newLocal(Type.getType("java/lang/StringBuilder"));
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
mv.visitInsn(Opcodes.DUP);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
// You need to put the top of the stack stringbuilder Save the pointer, or you won't find it later
mv.visitVarInsn(Opcodes.ASTORE, stringBuilderIndex);
mv.visitVarInsn(Opcodes.ALOAD, stringBuilderIndex);
mv.visitLdcInsn(className + "." + methodName + " time:");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitInsn(Opcodes.POP);
mv.visitVarInsn(Opcodes.ALOAD, stringBuilderIndex);
mv.visitVarInsn(Opcodes.LLOAD, timeLocalIndex);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
mv.visitInsn(Opcodes.POP);
mv.visitLdcInsn("Geek");
mv.visitVarInsn(Opcodes.ALOAD, stringBuilderIndex);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
// Be careful : Log.d The method returns a value , need pop get out
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "android/util/Log", "d", "(Ljava/lang/String;Ljava/lang/String;)I", false);
// 2
mv.visitInsn(Opcodes.POP);
}
First , stay onMethodEnter Comment in method 1 It's about , We call AdviceAdapter One of the parent classes of LocalVariablesSorter Of newLocal Method , It will create a new local variable according to the specified type , And directly assign a reference to a local variable index, Its advantage is that it can reuse the previous local variables as much as possible , We do not need to consider the allocation and coverage of local variables . then , stay onMethodExit Method, we can take the previous difference code and modify it properly , It should be noted that , In the comments 2 It's about , namely onMethodExit At the end of the method, we need to ensure the cleanness of the stack , Avoid leaving unused data at the top of the stack , If there is still data at the top of the stack , It will not only lead to exceptions in subsequent code , It will also affect the bytecode processing of other frameworks , Therefore, if there is data in the operand stack, it needs to be consumed or POP get out .
3、 Replace all in the project globally new Thread
First , We will first MainActivity Of startThread Method Thread The object changes to CustomThread, And then through ASM Bytecode Outline Of Show differences Check the difference in bytecode , As shown in the figure below :
We noticed that , First of all, I call NEW The opcode creates thread example , And then I called InvokeVirtual Operation code to execute thread Construction method of instance . Usually, these two instructions appear in pairs , however , Occasionally, I will encounter an existing instance passed from another location , And directly force the call of the construction method . therefore , We You need to judge in the code new and InvokeSpecial Whether they appear in pairs . The implementation code is as follows :
private final String methodName;
private final String className;
// Identify whether new Instructions
private boolean find = false;
protected TraceMethodAdapter(int api, MethodVisitor mv, int access, String name, String desc, String className) {
super(api, mv, access, name, desc);
this.className = className;
this.methodName = name;
}
@Override
public void visitTypeInsn(int opcode, String s) {
// Methods can be converted just like classes , for example , Forward by using a method adapter
// Those method calls with modifications : Changing parameters can be used to change instructions , Do not forward
// A method call can delete a An instruction , Inserting a new call can add a new instruction
if (opcode == Opcodes.NEW && "java/lang/Thread".equals(s)) {
// encounter new Instructions
find = true;
mv.visitTypeInsn(Opcodes.NEW, "com/sample/asm/CustomThread");
return;
}
super.visitTypeInsn(opcode, s);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
// Need to check CustomThread own
if ("java/lang/Thread".equals(owner) && !className.equals("com/sample/asm/CustomThread") && opcode == Opcodes.INVOKESPECIAL && find) {
find = false;
mv.visitMethodInsn(opcode, "com/sample/asm/CustomThread", name, desc, itf);
Log.e("asmcode", "className:%s, method:%s, name:%s", className, methodName, name);
return;
}
super.visitMethodInsn(opcode, owner, name, desc, itf);
}
In the use of ASM When inserting piles , In particular, we need to pay attention to the following At two o 'clock :
- 1)、 When we use ASM When processing bytecode , need Gradually modify a small amount 、 verification , Remember not to write a lot of bytecode and hope that they can be verified immediately and executed immediately . A safer approach is , When writing a line of bytecode, consider the changes between the operand stack and the local variable table , Write down another line after making sure it is correct . Besides , except JVM Beyond the verifier ,ASM A separate bytecode verifier is also maintained , It will also check whether your bytecode implementation conforms to JVM standard .
- 2)、 Pay attention to the data exchange between local variable table and operand stack and
try catch blcok
To deal with , For exception handling, you can use ASM Provided CheckClassAdapter, You can verify whether the bytecode is normal after the modification .
In addition to direct use ASM In addition to pile insertion , If the requirements are simple , We can use it based on ASM Bytecode processing tool , for example :lancet、Hunter and Hibeaver, At this time, the input-output ratio of using them will be higher .
5、 ... and 、 summary
stay ASM Bytecode Outline With the help of tools , We can complete many scenarios ASM The need for pile insertion , however , When we use it to process bytecode, we still need to consider many possible situations . If you want to have the deep thinking ability in this aspect , We will You must have a deep understanding of the characteristics of each opcode , If you don't know, you can go and have a look 《 Deeply explore the technology of compiling and inserting piles ( 3、 ... and 、JVM Bytecode ). therefore , Have the ability to implement a complex ASM The ability to insert piles , We need to be right about JVM Bytecode 、ASM Bytecode and ASM The implementation of the core tool class in the source code Be clear in your heart , And after continuous practice and trial and error , We can become a real ASM Pile inserting master .
Reference link :
2、《ASM 3.0 Guide translation 》PDF
4、AndroidAdvanceWithGeektime / Chapter07
5、Java Bytecode (Bytecode) And ASM Briefly explain
6、 The exploration of bytecode enhancement technology
7、 Exploration of bytecode manipulation technology
8、 Play together Android Bytecode in project
9、AndroidAdvanceWithGeektime / Chapter-ASM
10、IntelliJ plug-in unit - ASM Bytecode Outline
11、ASM Packaging Library :lancet、Hunter and Hibeaver
12、 be based on Javassist Bytecode processing tool :DroidAssist
13、 Except for modification during compilation class The way , In fact, we can also generate code during operation , For example, the popular runtime code generation library byte-buddy、byte-buddy Chinese document
author :jsonchao
link :https://juejin.cn/post/6844904118700474375
source : Rare earth digs gold
The copyright belongs to the author . Commercial reprint please contact the author for authorization , Non-commercial reprint please indicate the source .
边栏推荐
- Leetcode(547)——省份数量
- 【批处理DOS-CMD命令-汇总和小结】-跳转、循环、条件命令(goto、errorlevel、if、for[读取、切分、提取字符串]、)cmd命令错误汇总,cmd错误
- Distributed cache
- 【批处理DOS-CMD命令-汇总和小结】-字符串搜索、查找、筛选命令(find、findstr),Find和findstr的区别和辨析
- Common shortcuts to idea
- [yolov5 6.0 | 6.1 deploy tensorrt to torch serve] environment construction | model transformation | engine model deployment (detailed packet file writing method)
- mongodb客户端操作(MongoRepository)
- Equals() and hashcode()
- dynamic programming
- The programmer resigned and was sentenced to 10 months for deleting the code. Jingdong came home and said that it took 30000 to restore the database. Netizen: This is really a revenge
猜你喜欢
2021 SASE integration strategic roadmap (I)
AI super clear repair resurfaces the light in Huang Jiaju's eyes, Lecun boss's "deep learning" course survival report, beautiful paintings only need one line of code, AI's latest paper | showmeai info
学习光线跟踪一样的自3D表征Ego3RT
建立自己的网站(17)
Attention SLAM:一种从人类注意中学习的视觉单目SLAM
[C language] dynamic address book
Linear algebra of deep learning
Configuring OSPF basic functions for Huawei devices
@TableId can‘t more than one in Class: “com.example.CloseContactSearcher.entity.Activity“.
How to set encoding in idea
随机推荐
在jupyter中实现实时协同是一种什么体验
OSPF configuration command of Huawei equipment
Chapter II proxy and cookies of urllib Library
alexnet实验偶遇:loss nan, train acc 0.100, test acc 0.100情况
Linear algebra of deep learning
【批处理DOS-CMD命令-汇总和小结】-字符串搜索、查找、筛选命令(find、findstr),Find和findstr的区别和辨析
Distributed cache
Zabbix 5.0:通过LLD方式自动化监控阿里云RDS
Advanced learning of MySQL -- basics -- multi table query -- external connection
[daily problem insight] prefix and -- count the number of fertile pyramids in the farm
Notes of training courses selected by Massey school
[force buckle]41 Missing first positive number
Advanced learning of MySQL -- basics -- basic operation of transactions
学习光线跟踪一样的自3D表征Ego3RT
代码克隆的优缺点
Testers, how to prepare test data
深入探索编译插桩技术(四、ASM 探秘)
新手如何入门学习PostgreSQL?
St table
【批处理DOS-CMD命令-汇总和小结】-跳转、循环、条件命令(goto、errorlevel、if、for[读取、切分、提取字符串]、)cmd命令错误汇总,cmd错误