Java字节码增强应用

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

 

 

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注