JavaAgent技术是什么:自行搜索
为什么要学JavaAgent:破解Java软件,Agent内存马,RASP等
其中Agent的Jar由:Manifest,Agent Class,ClassFile Transformer组成
他们之间的关联大致如下:
xxxxxxxxxx
Manifest->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
方法存在
这个方法有两种使用方式
xxxxxxxxxx
public static void premain(String agentArgs, Instrumentation inst);
另一种
xxxxxxxxxx
public static void premain(String agentArgs);
实际上大多数情况下需要使用的是第一种方式,因为我们的目的是修改某个Class文件,如果需要修改Class文件则离不开Instrumentation
对象
其实JVM在加载Agent的时候,也是优先选择第一种方法,如果无法找到则会选择第二种
(2)DynamicAgent
注意到上文:DynamicAgent需要配置Agent-Class属性且要求agentmain
方法存在
这个方法也有两种使用方式
xxxxxxxxxx
public static void agentmain(String agentArgs, Instrumentation inst);
另一种
xxxxxxxxxx
public static void agentmain(String agentArgs);
可以发现与LoadTimeAgent几乎一致
上文提到:如果需要修改Class文件则离不开Instrumentation
对象
Instrumentation
是一个接口,关于这个接口更多的方法暂不介绍
先来看最基本的两个方法:添加和移除ClassFileTransformer
的方法
xxxxxxxxxx
public interface Instrumentation {
void
addTransformer(ClassFileTransformer transformer, boolean canRetransform);
boolean
removeTransformer(ClassFileTransformer transformer);
}
ClassFileTransformer`中有一个至关重要的方法:`transform
如果我们需要修改字节码,需要实现ClassFileTransformer
接口并重写transform
方法
xxxxxxxxxx
public interface ClassFileTransformer {
byte[]
transform( ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
throws IllegalClassFormatException;
}
目标:利用JavaAgent技术打印正在加载的类(LoadTimeAgent)
一个提供加法和减法的方法
xxxxxxxxxx
package 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;
}
}
一个简单的类:随机生成数字并做加减的运算
xxxxxxxxxx
package 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);
}
}
}
编译运行
xxxxxxxxxx
javac sample/*.java
java sample.Program
观察到如下的打印
xxxxxxxxxx
|000| 31968@DESKTOP-FP02BKH remains 060 seconds
a - b = 0
|001| 31968@DESKTOP-FP02BKH remains 059 seconds
a + b = 10
|002| 31968@DESKTOP-FP02BKH remains 058 seconds
a - b = 2
|003| 31968@DESKTOP-FP02BKH remains 057 seconds
a - b = 6
首先编写manifest.txt
文件(结尾必须是一个换行)
xxxxxxxxxx
Premain-Class: agent.LoadTimeAgent
编写AgentClass
xxxxxxxxxx
package 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
实现简单的打印功能
xxxxxxxxxx
package 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
xxxxxxxxxx
find ./src/main/java/ -name "*.java" > sources.txt
编译
xxxxxxxxxx
mkdir out && javac -d out/ @sources.txt
复制manifest.txt
文件到输出目录
xxxxxxxxxx
cp ./src/main/java/manifest.txt ./out/
使用命令生成Agent Jar
xxxxxxxxxx
jar -cvfm TheAgent.jar manifest.txt .
或
xxxxxxxxxx
jar -cvmf manifest.txt TheAgent.jar .
使用-javaagnet
启动,观察到很多打印内容说明成功
xxxxxxxxxx
java -javaagent:TheAgent.jar sample.Program
注意到上文的LoadTime示例并没有修改字节码
目标:打印出方法接收的参数,使用LoadTimeAgent实现
测试程序不变,由于调用打印方法需要修改字节码,所以编写ASM代码
这里使用JDK
自带的ASM框架,对于JDK8而言,ASM对应版本是5
xxxxxxxxxx
package cst;
import jdk.internal.org.objectweb.asm.Opcodes;
public class Const {
public static final int ASM_VERSION = Opcodes.ASM5;
}
该ClassVisitor的目的是遇到非构造方法和非静态代码块时,交给自定义MethodAdapter处理
xxxxxxxxxx
package 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
指令将每个参数入栈。之所以这样做是因为后续打印方法的调用需要弹栈,取出这个参数
xxxxxxxxxx
package 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
提供静态打印方法
xxxxxxxxxx
package 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
文件不变(结尾必须是一个换行)
xxxxxxxxxx
Premain-Class: agent.LoadTimeAgent
修改LoadTimeAgent
类的代码:使用inst.addTransformer
xxxxxxxxxx
package 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的一套流程对字节码进行修改即可
xxxxxxxxxx
package 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
xxxxxxxxxx
find ./src/main/java/ -name "*.java" > sources.txt
编译(当我们使用javac
编译时候并没有直接和rt.jar
做关联,而是使用到ct.sym
,如果需要指定使用rt.jar
则加上-XDignore.symbol.file
参数才可以,否则会报错)
xxxxxxxxxx
mkdir out && javac -XDignore.symbol.file -d out/ @sources.txt
复制manifest.txt
文件到输出目录
xxxxxxxxxx
cp ./src/main/java/manifest.txt ./out/
使用命令生成Agent Jar
xxxxxxxxxx
jar -cvfm TheAgent.jar manifest.txt .
或
xxxxxxxxxx
jar -cvmf manifest.txt TheAgent.jar .
使用-javaagnet
启动,观察到很多打印内容说明成功
xxxxxxxxxx
java -javaagent:TheAgent.jar sample.Program
打印如下
xxxxxxxxxx
Premain-Class: agent.LoadTimeAgent
60692@DESKTOP-FP02BKH
|000| 60692@DESKTOP-FP02BKH remains 060 seconds
Method Enter: sample/HelloWorld.add:(II)I
5
4
a + b = 9
以上两个示例都是LoadTimeAgent
这个示例将使用DynamicAgent
来实现和示例二一样的功能:打印方法参数
大部分代码都是相同的,区别在于Agent
的编写
编写DynamicAgent
需要在manifest.txt
中添加两个属性
xxxxxxxxxx
Agent-Class: agent.DynamicAgent
Can-Retransform-Classes: true
编写DynamicAgent
代码:注意retransform
之后要remove
了防止影响过多
xxxxxxxxxx
package 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
关于这个类相关的介绍将放在后文,本文只是使用
xxxxxxxxxx
package 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
所以建议分来来编译和打包
首先对测试程序本身进行编译
xxxxxxxxxx
mkdir out && javac src/main/java/sample/*.java -d out/
可以进入out
目录运行测试
xxxxxxxxxx
java sample.Program
对Agent进行打包
xxxxxxxxxx
find ./src/main/java/ -name "*.java" > sources.txt
这时候sources.txt
里会有VMAttach
的部分,这个编译会报错,所以手动删除
然后进行编译
xxxxxxxxxx
javac -XDignore.symbol.file -d out/ @sources.txt
复制manifest.txt
文件
xxxxxxxxxx
cp ./src/main/java/manifest.txt ./out/
使用命令生成Agent Jar
xxxxxxxxxx
jar -cvfm TheAgent.jar manifest.txt .
或
xxxxxxxxxx
jar -cvmf manifest.txt TheAgent.jar .
最后是对VMAttach
进行编译(这是针对GIT
模拟的Linux
命令行MINGW64
)
xxxxxxxxxx
javac -cp "${JAVA_HOME}/lib/tools.jar"\;. -d out/ src/main/java/attach/VMAttach.java
在Linux中应该是这样
xxxxxxxxxx
javac -cp "${JAVA_HOME}/lib/tools.jar":. -d out/ src/main/java/attach/VMAttach.java
纯Windows应该是这样
xxxxxxxxxx
javac -cp "%JAVA_HOME%/lib/tools.jar";. -d out/ src/main/java/attach/VMAttach.java
最后运行(同样分三种)
xxxxxxxxxx
java -cp "${JAVA_HOME}/lib/tools.jar"\;. attach.VMAttach
纯Linux
xxxxxxxxxx
java -cp "${JAVA_HOME}/lib/tools.jar":. attach.VMAttach
纯Windows
xxxxxxxxxx
java -cp "%JAVA_HOME%/lib/tools.jar";. attach.VMAttach
注意:需要先跑起来测试程序,然后再跑VMAttach
程序
如果VMAttach
打印如下且测试程序开始输出参数则说明成功
xxxxxxxxxx
Agent Path: TheAgent.jar
Load Agent
Detach
不难发现,自行编译打包非常复杂,所以迫切需要学习自动打包方式
maven-jar-plugin
+ maven-dependency-plugin
maven-assembly-plugin
maven-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的测试
xxxxxxxxxx
java -cp ./target/classes/ -javaagent:./target/TheAgent.jar sample.Program
对于DynamicAgent的测试
先启动测试程序
xxxxxxxxxx
java -cp ./target/classes/ sample.Program
再启动Agent
xxxxxxxxxx
java -cp "${JAVA_HOME}/lib/tools.jar"\;./target/classes/ run.DynamicInstrumentation
测试成功,用Maven自动打包的确比手动方便很多
(DynamicInstrumentation类就是上文的VMAttach类)