3

Java Agent 独立于应用程序的代理程序

 2 years ago
source link: https://mikeygithub.github.io/2021/12/29/yuque/ebpizv/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Java Agent 是一种特殊类型的类,通过使用 Java Instrumentation API,可以拦截在 JVM 上运行的应用程序,修改它们的字节码。Java 代理非常强大,但也很危险。

Java Agent 是基于工具的,来自 Java 平台,存在于 Java.lang 工具包,它提供了允许代理检测运行在 JVM 上的程序的服务。这个包非常简单且自包含,因为它包含一对异常类、一个数据类、类定义和两个接口。在这两个接口中,如果我们想要编写 Java 代理,我们只需要实现classFileTransformer接口。

两种实现方法

这意味着我们构建我们的代理,我们将它打包为一个 jar 文件,当我们启动我们的 Java 应用程序时,我们传入一个特殊的 JVM 参数,称为javaagent。然后,我们给它提供代理 jar 文件在磁盘上的位置。

1.创建一个 agent 的项目
1.1 创建 agent 类

@Slf4j
public class JavaAgent {
public static void premain(String agentArgs, Instrumentation instrumentation) {
log.info("execute premain args:{}",agentArgs);
ClassFileTransformer interceptingClassTransformer = (loader, className, classBeingRedefined, protectionDomain, classfileBuffer) -> {
log.info("ClassLoader:{} className:{}",loader,className);
return classfileBuffer;
};
instrumentation.addTransformer(interceptingClassTransformer,true);
}
}

1.2 创建 MATE-INF/MANIFEST.MF 文件

Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: io.demo.agent.JavaAgent

1.3 配置 pom 文件

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<archive>
<!--自动添加META-INF/MANIFEST.MF -->
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Premain-Class>io.demo.agent.JavaAgent</Premain-Class>
<Agent-Class>io.demo.agent.JavaAgent</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>

1.4 将 agent 项目打成 jar 包

mvm clean package

2.创建另一个可运行的项目 execute

2.1 创建一个可运行的类

@Slf4j
public class Execute {
public static void main(String[] args) {
log.info("i am execute main");
}
}

2.2 配置 pom 文件

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<!--这里写你的main函数所在的类的路径名,也就是Class.forName的那个字符串-->
<mainClass>io.demo.execute.Execute</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

2.3 运行命令

java -javaagent:agent-1.0-SNAPSHOT.jar -jar execute-1.0-SNAPSHOT.jar

2.4 查看输出

与启动应用程序的方式不同,您可以编写一小段代码,获取并连接到现有的 JVM,并告诉它加载某个代理。

public class DynamicLoadAgent {
public static final String AGENT_FILE_PATH = "/Users/biaoyang/IdeaProjects/java-agent/agent/target/agent-1.0-SNAPSHOT.jar";
public static void main(String[] args) throws AgentLoadException, IOException, AgentInitializationException, AttachNotSupportedException {
//通过pid连接进入指定java程序jvm
VirtualMachine vm = VirtualMachine.attach("4895");//pid可通过jps查看或VirtualMachine.list()
//加载agent
vm.loadAgent(AGENT_FILE_PATH);
//脱离虚拟机
vm.detach();
}
}

这个参数 agentFilePath 与静态代理方法中的参数完全相同。它必须是 agent jar 的文件名,因此没有输入流就没有字节。对于这种方法有两个注意事项。第一个这是 sun 包下的私有 API,通常适用于热点实现。第二个是,用 java 9,你不能再使用这些代码来连接它运行的 JVM。

Premain 方法有两个参数:

  • agentArgs:字符串参数,用户选择将其作为参数传递给 Java 代理调用。
  • Instrumentation:来自 java.lang 包,我们可以添加一个新的 ClassFileTransformer 对象,它包含我们的代理的实际逻辑。
//会在用户类加载前先执行
//优先选择这个方法执行
public static void premain(String agentArgs, Instrumentation instrumentation)
public static void premain(String agentArgs)
//JDK1.6以后新增的,可以实现在main方法执行以后进行插入执行。
public static void agentmain (String agentArgs, Instrumentation inst)
public static void agentmain (String agentArgs)

通过 javassist 实现修改字节码的操作。

1.编写 agent

@Slf4j
public class JavaAgent {
public static void premain(String agentArgs, Instrumentation instrumentation) {
log.info("execute premain args:{}", agentArgs);
ClassFileTransformer interceptingClassTransformer = (loader, className, classBeingRedefined, protectionDomain, classfileBuffer) -> {

log.info("ClassLoader:{} className:{}", loader, className);
if ("io/demo/execute/Execute".equals(className)) {
try {
// 从ClassPool获得CtClass对象
log.info("aaaaaaaa");
final ClassPool classPool = ClassPool.getDefault();
log.info("ClassPool={}", classPool);
final CtClass clazz = classPool.get("io.demo.execute.Execute");
log.info("clazz={}", clazz);
CtMethod convertToAbbr = clazz.getDeclaredMethod("getInfo");
// 覆盖方法体
String methodBody = "{return \"aget update\";}";
convertToAbbr.setBody(methodBody);
// 返回字节码,并且detachCtClass对象
byte[] byteCode = clazz.toBytecode();
//从ClassPool中移除这个CtClass对象
clazz.detach();
return byteCode;
} catch (Throwable ex) {
ex.printStackTrace();
}
}
return null;
};
instrumentation.addTransformer(interceptingClassTransformer);
}
}

2.执行的类

@Slf4j
public class Execute {
public static void main(String[] args) {
log.info("i am execute main");
log.error(getInfo());
}
public static String getInfo(){
return "Execute getInfo";
}
}
java -javaagent:agent-1.0-SNAPSHOT.jar -jar execute-1.0-SNAPSHOT.jar

4.执行结果

什么是 Java Agent ?: https://www.developer.com/design/what-is-java-agent/

Understanding Java Agents:https://dzone.com/articles/java-agent-1


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK