Java字节码增强技术
认识Instrumentation
基本用法:
方法 | 作用 | |
addTransformer | 织入一段代码 | |
removeTransformer | 移除被织入的代码 | |
retransformClasses | 用于已经载入的类重新植入 | Class<?>... classes |
其他用法:
方法 | 作用 | |
appendToBootstrapClassLoaderSearch | 将指定路径的jar包用bootstrapclassloader查找路径 | |
getAllLoadedClasses | 获取所有已加载的类 | |
getObjectSize | 获取指定的对象的大小 | |
isNativeMethodPrefixSupported | 是否拦截本地方法 |
Permain的方式植入代码
程序测试代码
package huster.top;
/**
* Created by longan.rtb on 2019/7/8.
*/
public class JavaAgentTest {
public String helloWorld() {
for (int i = 0; i<5; i++) {
System.out.println("Hello world!");
}
return "ok";
}
public static void main(String[] args) {
JavaAgentTest javaAgentTest = new JavaAgentTest();
javaAgentTest.helloWorld();
}
}
Agent代码(permain方式)
package huster.top;
import java.lang.instrument.Instrumentation;
/**
* Created by longan.rtb on 2019/7/8.
*/
public class AgentMain {
public static void premain(String args, Instrumentation inst) {
System.out.println("permain has been called, arguments is : " + args);
inst.addTransformer(new AOPImplement());
}
}
public class AOPImplement implements ClassFileTransformer {
private final static String methodName = "helloWorld";
private final static String targetClassName = "huster.top.JavaAgentTest";
public byte[]
transform( ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
throws IllegalClassFormatException {
className = className.replace("/", ".");
if (!className.equals(targetClassName)) {
//只监控需要的类
return classfileBuffer;
}
CtClass ctclass = null;
try {
ctclass = ClassPool.getDefault().get(className);
//只监控需要的方法
CtMethod ctmethod = ctclass.getDeclaredMethod(methodName);
//旧方法改个名字
String oldMethod = methodName + "_____old____";
ctmethod.setName(oldMethod);
CtMethod newMethod = CtNewMethod.copy(ctmethod, methodName, ctclass, null);
String methodBody = "{" +
"System.out.println(\"before called!!\");\n" +
"String a = " + oldMethod + "($$);\n" +
"System.out.println(\"after method called!!\");\n" +
"return a;" +
"}";
newMethod.setBody(methodBody);
ctclass.addMethod(newMethod);
return ctclass.toBytecode();
} catch (Exception e) {
e.printStackTrace();
return classfileBuffer;
}
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>javaagent-demo</artifactId>
<groupId>huster.top</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>original-agent</artifactId>
<dependencies>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.1.GA</version>
</dependency>
</dependencies>
<build>
<finalName>original-agent</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Premain-Class>huster.top.AgentMain</Premain-Class>
</manifestEntries>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
然后在测试代码的vm参数里增加:
-javaagent:/Users/longan.rtb/JavaProject/javaagentdemo/originalagent/target/original-agent-jar-with-dependencies.jar=args1;args2;args3
运行结果如下:
Attach的方式植入代码
attach的程序
public class AttachAgent {
public static void main(String[] args) throws Exception {
String pid = args[0];
String jar = args[1];
if (jar == null || "".equals(jar)) {
jar = "/Users/longan.rtb/JavaProject/javaagentdemo/originalagent/target/original-agent-jar-with-dependencies.jar";
}
VirtualMachine vmObj = null;
try {
vmObj = VirtualMachine.attach(pid);
vmObj.loadAgent(jar,
"this is arguments");
} finally {
if (vmObj != null) {
vmObj.detach();
}
}
}
}
agent的代码修改为
public class AgentMain {
public static void premain(String args, Instrumentation inst) {
System.out.println("permain has been called, arguments is : " + args);
inst.addTransformer(new AOPImplement());
}
//attach方式.
public static void agentmain(String args, Instrumentation inst){
System.out.println("444agentmain has been called, arguments is : " + args);
System.out.println("is enable :" + String.valueOf(inst.isRetransformClassesSupported()));
inst.addTransformer(new AOPImplement2(),true);
try {
inst.retransformClasses(Class.forName("huster.top.JavaAgentTest"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
坑1: 始终报UnsupportedOperationException错误
是因为我们在植入代码的时候,使用了新增函数的方法,这种方法的是不允许的在Retrans的时候
* Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method* The retransformation may change method bodies, the constant pool and attributes.* The retransformation must not add, remove or rename fields or methods, change the* signatures of methods, or change inheritance. These restrictions maybe be* lifted in future versions. The class file bytes are not checked, verified and installed* until after the transformations have been applied, if the resultant bytes are in* error this method will throw an exception.
将attach程序打包成jar运行
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>javaagent-demo</artifactId>
<groupId>huster.top</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>attach-agent</artifactId>
<dependencies>
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>attached</goal>
</goals>
<phase>package</phase>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>huster.top.AttachAgent</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
坑2: VirtualMachine找不到的问题
需要我们在运行的时候指明tool.jar的位置
/Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home/bin/java -jar -Xbootclasspath/a:/Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home/lib/tools.jar /Users/longan.rtb/JavaProject/javaagentdemo/attachagent/target/attach-agent-1.0-SNAPSHOT-jar-with-dependencies.jar 29808