JavaAgent技术是什么:自行搜索
为什么要学JavaAgent:破解Java软件,Agent内存马,RASP等
其中Agent的Jar由:Manifest,Agent Class,ClassFile Transformer组成
他们之间的关联大致如下:
xxxxxxxxxxManifest->AgentClass->Instrumentation->ClassFileTransformer->ASM根据Mainifest中定义的属性找到具体的AgentClass,调用对应的方法
在方法中通过Instrumentation对象使用ClassFileTransformer
而操作字节码则需要了解Class文件和ASM或Javassist的使用
首先来看Manifest中与Java Agent相关的属性
这里做一个简单的介绍,对相关属性有大体的了解,结合后续实例进一步理解
(1)Premain-Class
使用Java Agent有两种方式:Load-Time和Dynamic,也就是说一种是启动时,一种是运行时。当我们使用启动时Agent需要加入-javaagent参数,这时候就会在Manifest中寻找Premain-Class属性
这个属于记录的是一个类名,要求这个类中必须包含premain方法
(2)Agent-Class
使用另一种Dynamic方式的Java Agent需要寻找Manifest的Agent-Class属性
该属性记录的也是一个类名,要求这个类中必须包含agentmain方法
(3)Can-Redefine-Classes
这是一个Boolean类型的属性,默认False
判断当前JVM的配置是否支持类的重新定义(后续介绍)
(4)Can-Retransform-Classes
这是一个Boolean类型的属性,默认False
如果需要调用Instrumentation.retransformClasses等方法需要设置为True
(5)Can-Set-Native-Method-Prefix
这是一个Boolean类型的属性,默认False
是否支持设置本地方法前(后续介绍)
(6)Class-Path与Boot-Class-Path
JVM有三个ClassLoader:Bootstrap,Extension,Application(System)
其中Class-Path指定的Jar包将由Application(System) ClassLoader加载
其中Boot-Class-Path指定的Jar包将由Bootstrap ClassLoader加载
之所以要提到Boot-Class-Path属性,因为在某些情况下必须使用该属性
(7)Launcher-Agent-Class
该属性是Java9引用的属性,记录的是一个类名,类似Premain-Class和Agent-Class
可用于在main方法执行之前做一些事情
(1)LoadTimeAgent
注意到上文:LoadTimeAgent需要配置Premain-Class属性且要求premain方法存在
这个方法有两种使用方式
xxxxxxxxxxpublic static void premain(String agentArgs, Instrumentation inst);另一种
xxxxxxxxxxpublic static void premain(String agentArgs);实际上大多数情况下需要使用的是第一种方式,因为我们的目的是修改某个Class文件,如果需要修改Class文件则离不开Instrumentation对象
其实JVM在加载Agent的时候,也是优先选择第一种方法,如果无法找到则会选择第二种
(2)DynamicAgent
注意到上文:DynamicAgent需要配置Agent-Class属性且要求agentmain方法存在
这个方法也有两种使用方式
xxxxxxxxxxpublic static void agentmain(String agentArgs, Instrumentation inst);另一种
xxxxxxxxxxpublic static void agentmain(String agentArgs);可以发现与LoadTimeAgent几乎一致
上文提到:如果需要修改Class文件则离不开Instrumentation对象
Instrumentation是一个接口,关于这个接口更多的方法暂不介绍
先来看最基本的两个方法:添加和移除ClassFileTransformer的方法
xxxxxxxxxxpublic interface Instrumentation { void addTransformer(ClassFileTransformer transformer, boolean canRetransform);
boolean removeTransformer(ClassFileTransformer transformer);}ClassFileTransformer`中有一个至关重要的方法:`transform如果我们需要修改字节码,需要实现ClassFileTransformer接口并重写transform方法
xxxxxxxxxxpublic interface ClassFileTransformer { byte[] transform( ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException;}
目标:利用JavaAgent技术打印正在加载的类(LoadTimeAgent)
一个提供加法和减法的方法
xxxxxxxxxxpackage sample;
public class HelloWorld { public static int add(int a, int b) { return a + b; }
public static int sub(int a, int b) { return a - b; }}一个简单的类:随机生成数字并做加减的运算
xxxxxxxxxxpackage sample;
import java.lang.management.ManagementFactory;import java.util.Random;import java.util.concurrent.TimeUnit;
public class Program { public static void main(String[] args) throws Exception { String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName(); System.out.println(nameOfRunningVM); int count = 600; for (int i = 0; i < count; i++) { String info = String.format("|%03d| %s remains %03d seconds", i, nameOfRunningVM, (count - i)); System.out.println(info);
Random rand = new Random(System.currentTimeMillis()); int a = rand.nextInt(10); int b = rand.nextInt(10); boolean flag = rand.nextBoolean(); String message; if (flag) { message = String.format("a + b = %d", HelloWorld.add(a, b)); } else { message = String.format("a - b = %d", HelloWorld.sub(a, b)); } System.out.println(message);
TimeUnit.SECONDS.sleep(1); } }}编译运行
xxxxxxxxxxjavac sample/*.javajava sample.Program观察到如下的打印
xxxxxxxxxx|000| 31968@DESKTOP-FP02BKH remains 060 secondsa - b = 0|001| 31968@DESKTOP-FP02BKH remains 059 secondsa + b = 10|002| 31968@DESKTOP-FP02BKH remains 058 secondsa - b = 2|003| 31968@DESKTOP-FP02BKH remains 057 secondsa - b = 6
首先编写manifest.txt文件(结尾必须是一个换行)
xxxxxxxxxxPremain-Class: agent.LoadTimeAgent编写AgentClass
xxxxxxxxxxpackage agent;
import instrument.InfoTransformer;
import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.Instrumentation;
public class LoadTimeAgent { public static void premain(String agentArgs, Instrumentation inst){ System.out.println("Premain-Class: "+LoadTimeAgent.class.getName()); ClassFileTransformer transformer = new InfoTransformer(); inst.addTransformer(transformer); }}编写ClassFileTransformer实现简单的打印功能
xxxxxxxxxxpackage instrument;
import java.lang.instrument.ClassFileTransformer;import java.security.ProtectionDomain;import java.util.Formatter;
public class InfoTransformer implements ClassFileTransformer { public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { StringBuilder sb = new StringBuilder(); Formatter fm = new Formatter(sb); fm.format("ClassName: %s%n", className); fm.format("\tClassLoader: %s%n", loader); fm.format("\tClassBeingRedefined: %s%n", classBeingRedefined); fm.format("\tProtectionDomain: %s%n", protectionDomain); System.out.println(sb); return null; }}
找到所有的Java文件并将文件名写入sources.txt
xxxxxxxxxxfind ./src/main/java/ -name "*.java" > sources.txt编译
xxxxxxxxxxmkdir out && javac -d out/ @sources.txt复制manifest.txt文件到输出目录
xxxxxxxxxxcp ./src/main/java/manifest.txt ./out/使用命令生成Agent Jar
xxxxxxxxxxjar -cvfm TheAgent.jar manifest.txt .或
xxxxxxxxxxjar -cvmf manifest.txt TheAgent.jar .使用-javaagnet启动,观察到很多打印内容说明成功
xxxxxxxxxxjava -javaagent:TheAgent.jar sample.Program
注意到上文的LoadTime示例并没有修改字节码
目标:打印出方法接收的参数,使用LoadTimeAgent实现
测试程序不变,由于调用打印方法需要修改字节码,所以编写ASM代码
这里使用JDK自带的ASM框架,对于JDK8而言,ASM对应版本是5
xxxxxxxxxxpackage cst;
import jdk.internal.org.objectweb.asm.Opcodes;
public class Const { public static final int ASM_VERSION = Opcodes.ASM5;}该ClassVisitor的目的是遇到非构造方法和非静态代码块时,交给自定义MethodAdapter处理
xxxxxxxxxxpackage asm;
import cst.Const;import jdk.internal.org.objectweb.asm.ClassVisitor;import jdk.internal.org.objectweb.asm.MethodVisitor;
public class MethodInfoVisitor extends ClassVisitor { private String owner;
public MethodInfoVisitor(ClassVisitor classVisitor) { super(Const.ASM_VERSION, classVisitor); }
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces); this.owner = name; }
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); if (mv != null && !name.equals("<init>") && !name.equals("<clinit>")) { mv = new MethodInfoAdapter(mv, owner, access, name, descriptor); } return mv; }}在对应的MethodAdapter中,逻辑是这样:一开始进入方法时,打印方法名等信息,然后遍历方法参数,使用ILOAD指令将每个参数入栈。之所以这样做是因为后续打印方法的调用需要弹栈,取出这个参数
xxxxxxxxxxpackage asm;
import cst.Const;import jdk.internal.org.objectweb.asm.MethodVisitor;import jdk.internal.org.objectweb.asm.Opcodes;import jdk.internal.org.objectweb.asm.Type;
public class MethodInfoAdapter extends MethodVisitor { private final String owner; private final int methodAccess; private final String methodName; private final String methodDesc;
public MethodInfoAdapter(MethodVisitor methodVisitor, String owner, int methodAccess, String methodName, String methodDesc) { super(Const.ASM_VERSION, methodVisitor); this.owner = owner; this.methodAccess = methodAccess; this.methodName = methodName; this.methodDesc = methodDesc; }
public void visitCode() { if (mv != null) { String line = String.format("Method Enter: %s.%s:%s", owner, methodName, methodDesc); printMessage(line); int slotIndex = (methodAccess & Opcodes.ACC_STATIC) != 0 ? 0 : 1; Type methodType = Type.getMethodType(methodDesc); Type[] argumentTypes = methodType.getArgumentTypes(); for (Type t : argumentTypes) { int sort = t.getSort(); int size = t.getSize(); int opcode = t.getOpcode(Opcodes.ILOAD); super.visitVarInsn(opcode, slotIndex);
if (sort >= Type.BOOLEAN && sort <= Type.DOUBLE) { String desc = t.getDescriptor(); printValueOnStack("(" + desc + ")V"); } else { printValueOnStack("(Ljava/lang/Object;)V"); } slotIndex += size; } }
super.visitCode(); }
private void printMessage(String str) { super.visitLdcInsn(str); super.visitMethodInsn(Opcodes.INVOKESTATIC, "utils/ParameterUtils", "printText", "(Ljava/lang/String;)V", false); }
private void printValueOnStack(String descriptor) { super.visitMethodInsn(Opcodes.INVOKESTATIC, "utils/ParameterUtils", "printValueOnStack", descriptor, false); }
private void printStackTrace() { super.visitMethodInsn(Opcodes.INVOKESTATIC, "utils/ParameterUtils", "printStackTrace", "()V", false); }}由于System.out.println打印方法实际上对应的字节码指令比较复杂,所以自行写了一个ParameterUtils提供静态打印方法
xxxxxxxxxxpackage utils;
import java.text.DateFormat;import java.text.SimpleDateFormat;import java.util.Arrays;import java.util.Date;
public class ParameterUtils { private static final DateFormat fm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private ParameterUtils() { throw new UnsupportedOperationException(); }
public static void printValueOnStack(boolean value) { System.out.println(" " + value); }
public static void printValueOnStack(byte value) { System.out.println(" " + value); }
public static void printValueOnStack(char value) { System.out.println(" " + value); }
public static void printValueOnStack(short value) { System.out.println(" " + value); }
public static void printValueOnStack(int value) { System.out.println(" " + value); }
public static void printValueOnStack(float value) { System.out.println(" " + value); }
public static void printValueOnStack(long value) { System.out.println(" " + value); }
public static void printValueOnStack(double value) { System.out.println(" " + value); }
public static void printValueOnStack(Object value) { if (value == null) { System.out.println(" " + null); } else if (value instanceof String) { System.out.println(" " + value); } else if (value instanceof Date) { System.out.println(" " + fm.format(value)); } else if (value instanceof char[]) { System.out.println(" " + Arrays.toString((char[]) value)); } else if (value instanceof Object[]) { System.out.println(" " + Arrays.toString((Object[]) value)); } else { System.out.println(" " + value.getClass() + ": " + value); } }
public static void printText(String str) { System.out.println(str); }
public static void printStackTrace() { Exception ex = new Exception(); ex.printStackTrace(System.out); }}
其中manifest.txt文件不变(结尾必须是一个换行)
xxxxxxxxxxPremain-Class: agent.LoadTimeAgent修改LoadTimeAgent类的代码:使用inst.addTransformer
xxxxxxxxxxpackage agent;
import instrument.ASMTransformer;
import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.Instrumentation;
public class LoadTimeAgent { public static void premain(String agentArgs, Instrumentation inst) { System.out.println("Premain-Class: " + LoadTimeAgent.class.getName()); ClassFileTransformer transformer = new ASMTransformer(); inst.addTransformer(transformer); }}编写ASMTransformer代码:由于我们只处理sample/HelloWorld类所以判断类名是否equals然后用ASM的一套流程对字节码进行修改即可
xxxxxxxxxxpackage instrument;
import asm.MethodInfoVisitor;import jdk.internal.org.objectweb.asm.ClassReader;import jdk.internal.org.objectweb.asm.ClassVisitor;import jdk.internal.org.objectweb.asm.ClassWriter;
import java.lang.instrument.ClassFileTransformer;import java.security.ProtectionDomain;
public class ASMTransformer implements ClassFileTransformer { public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { if (className.equals("sample/HelloWorld")) { ClassReader cr = new ClassReader(classfileBuffer); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassVisitor cv = new MethodInfoVisitor(cw); int parsingOptions = 0; cr.accept(cv, parsingOptions); return cw.toByteArray(); } return null; }}
找到所有的Java文件并将文件名写入sources.txt
xxxxxxxxxxfind ./src/main/java/ -name "*.java" > sources.txt编译(当我们使用javac编译时候并没有直接和rt.jar做关联,而是使用到ct.sym,如果需要指定使用rt.jar则加上-XDignore.symbol.file参数才可以,否则会报错)
xxxxxxxxxxmkdir out && javac -XDignore.symbol.file -d out/ @sources.txt复制manifest.txt文件到输出目录
xxxxxxxxxxcp ./src/main/java/manifest.txt ./out/使用命令生成Agent Jar
xxxxxxxxxxjar -cvfm TheAgent.jar manifest.txt .或
xxxxxxxxxxjar -cvmf manifest.txt TheAgent.jar .使用-javaagnet启动,观察到很多打印内容说明成功
xxxxxxxxxxjava -javaagent:TheAgent.jar sample.Program打印如下
xxxxxxxxxxPremain-Class: agent.LoadTimeAgent60692@DESKTOP-FP02BKH|000| 60692@DESKTOP-FP02BKH remains 060 secondsMethod Enter: sample/HelloWorld.add:(II)I 5 4a + b = 9
以上两个示例都是LoadTimeAgent
这个示例将使用DynamicAgent来实现和示例二一样的功能:打印方法参数
大部分代码都是相同的,区别在于Agent的编写
编写DynamicAgent需要在manifest.txt中添加两个属性
xxxxxxxxxxAgent-Class: agent.DynamicAgentCan-Retransform-Classes: true编写DynamicAgent代码:注意retransform之后要remove了防止影响过多
xxxxxxxxxxpackage agent;
import instrument.ASMTransformer;
import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.Instrumentation;
public class DynamicAgent { public static void agentmain(String agentArgs, Instrumentation inst) { ClassFileTransformer transformer = new ASMTransformer(); try { inst.addTransformer(transformer, true); Class<?> targetClass = Class.forName("sample.HelloWorld"); inst.retransformClasses(targetClass); } catch (Exception ex) { ex.printStackTrace(); } finally { inst.removeTransformer(transformer); } }}使用DynamicAgent需要特殊的一个类VMAttach
关于这个类相关的介绍将放在后文,本文只是使用
xxxxxxxxxxpackage attach;
import com.sun.tools.attach.VirtualMachine;import com.sun.tools.attach.VirtualMachineDescriptor;
import java.util.List;
public class VMAttach { public static void main(String[] args) throws Exception { String agent = "TheAgent.jar"; System.out.println("Agent Path: " + agent); List<VirtualMachineDescriptor> vmds = VirtualMachine.list(); for (VirtualMachineDescriptor vmd : vmds) { if (vmd.displayName().equals("sample.Program")) { VirtualMachine vm = VirtualMachine.attach(vmd.id()); System.out.println("Load Agent"); vm.loadAgent(agent); System.out.println("Detach"); vm.detach(); } } }}
对于DynamicAgent而言想要打包其实比较麻烦,例如上文的VMAttach类需要tools.jar
所以建议分来来编译和打包
首先对测试程序本身进行编译
xxxxxxxxxxmkdir out && javac src/main/java/sample/*.java -d out/可以进入out目录运行测试
xxxxxxxxxxjava sample.Program对Agent进行打包
xxxxxxxxxxfind ./src/main/java/ -name "*.java" > sources.txt这时候sources.txt里会有VMAttach的部分,这个编译会报错,所以手动删除
然后进行编译
xxxxxxxxxxjavac -XDignore.symbol.file -d out/ @sources.txt复制manifest.txt文件
xxxxxxxxxxcp ./src/main/java/manifest.txt ./out/使用命令生成Agent Jar
xxxxxxxxxxjar -cvfm TheAgent.jar manifest.txt .或
xxxxxxxxxxjar -cvmf manifest.txt TheAgent.jar .最后是对VMAttach进行编译(这是针对GIT模拟的Linux命令行MINGW64)
xxxxxxxxxxjavac -cp "${JAVA_HOME}/lib/tools.jar"\;. -d out/ src/main/java/attach/VMAttach.java在Linux中应该是这样
xxxxxxxxxxjavac -cp "${JAVA_HOME}/lib/tools.jar":. -d out/ src/main/java/attach/VMAttach.java纯Windows应该是这样
xxxxxxxxxxjavac -cp "%JAVA_HOME%/lib/tools.jar";. -d out/ src/main/java/attach/VMAttach.java最后运行(同样分三种)
xxxxxxxxxxjava -cp "${JAVA_HOME}/lib/tools.jar"\;. attach.VMAttach纯Linux
xxxxxxxxxxjava -cp "${JAVA_HOME}/lib/tools.jar":. attach.VMAttach纯Windows
xxxxxxxxxxjava -cp "%JAVA_HOME%/lib/tools.jar";. attach.VMAttach注意:需要先跑起来测试程序,然后再跑VMAttach程序
如果VMAttach打印如下且测试程序开始输出参数则说明成功
xxxxxxxxxxAgent Path: TheAgent.jarLoad AgentDetach
不难发现,自行编译打包非常复杂,所以迫切需要学习自动打包方式
maven-jar-plugin + maven-dependency-pluginmaven-assembly-pluginmaven-shade-plugin加入必须的依赖(可以用ASM库而不是JDK内置了)
xxxxxxxxxx<dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> <version>${asm.version}</version></dependency>一些属性配置
xxxxxxxxxx<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> <maven.compiler.source>${java.version}</maven.compiler.source> <maven.compiler.target>${java.version}</maven.compiler.target> <asm.version>9.2</asm.version></properties>由于DynamicAgent需要tools.jar所以另外添加
xxxxxxxxxx<dependency> <groupId>com.sun</groupId> <artifactId>tools</artifactId> <version>8</version> <scope>system</scope> <systemPath>${env.JAVA_HOME}/lib/tools.jar</systemPath></dependency>编译插件(必须)
xxxxxxxxxx<build> <!-- 最终编译JAR名字 --> <finalName>TheAgent</finalName> <plugins> <!-- Java Compiler --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> <fork>true</fork> <compilerArgs> <!-- 生成所有调试信息 --> <arg>-g</arg> <!-- 生成MethodParameter属性 --> <arg>-parameters</arg> <!-- 之前介绍过使用rt.jar --> <arg>-XDignore.symbol.file</arg> </compilerArgs> </configuration> </plugin> </plugins></build>
JAR插件
xxxxxxxxxx<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.2.0</version> <configuration> <archive> <manifest> <!-- 主类名(不是必须) --> <mainClass>myagent.Main</mainClass> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <addDefaultImplementationEntries>false</addDefaultImplementationEntries> <addDefaultSpecificationEntries>false</addDefaultSpecificationEntries> </manifest> <!-- Agent相关的属性 --> <manifestEntries> <Premain-Class>myagent.agent.LoadTimeAgent</Premain-Class> <Agent-Class>myagent.agent.DynamicAgent</Agent-Class> <Launcher-Agent-Class>myagent.agent.LauncherAgent</Launcher-Agent-Class> <!-- 防止出问题直接给三个属性都设为true --> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> <Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix> </manifestEntries> <addMavenDescriptor>false</addMavenDescriptor> </archive> <!-- 只将指定目录打包 --> <includes> <include>myagent/**</include> </includes> </configuration></plugin>这时候如果执行代码会出问题,找不到依赖
所以需要另一个插件:作用是把所有依赖复制过来
xxxxxxxxxx<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>3.2.0</version> <executions> <execution> <id>lib-copy-dependencies</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <excludeArtifactIds>tools</excludeArtifactIds> <outputDirectory>${project.build.directory}/lib</outputDirectory> <overWriteReleases>false</overWriteReleases> <overWriteSnapshots>false</overWriteSnapshots> <overWriteIfNewer>true</overWriteIfNewer> </configuration> </execution> </executions></plugin>这时候就可以运行Agent观察效果了
另一种办法是使用assembly把所有jar都打包进去
xxxxxxxxxx<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.3.0</version> <configuration> <archive> <manifest> <mainClass>myagent.Main</mainClass> <addDefaultEntries>false</addDefaultEntries> </manifest> <manifestEntries> <Premain-Class>myagent.agent.LoadTimeAgent</Premain-Class> <Agent-Class>myagent.agent.DynamicAgent</Agent-Class> <Launcher-Agent-Class>myagent.agent.LauncherAgent</Launcher-Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> <Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix> </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>
使用shade插件,可以在assembly基础上进一步缩小体积
xxxxxxxxxx<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.4</version> <configuration> <minimizeJar>true</minimizeJar> <filters> <filter> <artifact>*:*</artifact> <!-- 排除 --> <excludes> <exclude>run/*</exclude> <exclude>sample/*</exclude> </excludes> </filter> </filters> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <manifestEntries> <Main-Class>myagent.Main</Main-Class> <Premain-Class>myagent.agent.LoadTimeAgent</Premain-Class> <Agent-Class>myagent.agent.DynamicAgent</Agent-Class> <Launcher-Agent-Class>myagent.agent.LauncherAgent</Launcher-Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> <Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix> </manifestEntries> </transformer> </transformers> </configuration> </execution> </executions></plugin>
对于LoadTimeAgent的测试
xxxxxxxxxxjava -cp ./target/classes/ -javaagent:./target/TheAgent.jar sample.Program对于DynamicAgent的测试
先启动测试程序
xxxxxxxxxxjava -cp ./target/classes/ sample.Program再启动Agent
xxxxxxxxxxjava -cp "${JAVA_HOME}/lib/tools.jar"\;./target/classes/ run.DynamicInstrumentation测试成功,用Maven自动打包的确比手动方便很多
(DynamicInstrumentation类就是上文的VMAttach类)