SandBox中的JavaAgent技术

8.1 JavaAgent

Java Agent 是一种强大的技术,在运行时动态修改已加载类的字节码,为应用程序注入额外的功能和行为。

JDK 1.5 支持静态 Instrumentation,基本的思路是在 JVM 启动的时候添加一个代理(javaagent),每个代理是一个 jar 包,其 MANIFEST.MF 文件里指定了代理类,这个代理类包含一个 premain 方法。JVM 在类加载时候会先执行代理类的 premain 方法,再执行 Java 程序本身的 main 方法,这就是 premain 名字的来源。在 premain 方法中可以对加载前的 class 文件进行修改。

8.1.1 应用场景:

代码注入:通过 Java Agent 技术,我们可以在运行时将自定义的字节码注入到已加载的类中。这样可以实现 AOP 编程思想,将通用的逻辑代码动态地注入到目标类中,简化代码的编写和维护。

性能监控:Java Agent 可以用于实时监控应用程序的性能指标,如方法的执行时间、方法的调用次数等。通过在方法的前后插入字节码,我们可以收集这些信息,并进行实时监控、统计和分析,帮助优化和调优应用程序的性能。

调试工具:Java Agent 可以用作调试工具的基础。我们可以通过动态修改字节码,将额外的调试信息插入到目标类中,例如打印方法的参数、返回值等。这样,在调试过程中可以更加方便地获取和分析这些信息,帮助快速定位问题。

安全检查:Java Agent 还可以用于实现安全检查机制。通过对正在加载的类进行拦截和检查,我们可以在运行时进行代码审计、防止恶意代码注入等,提高应用程序的安全性。

8.1.2 启动方式

启动挂载(Agent):

所有的类在加载时都会执行transform,我们在此方法中通过 Instrumentation 增强目标类。

随着JVM启动一起启动(java -javaagent:(jar包名) (方法类名))

premain(): 实现premain方法,并且添加一个自定义transformer,把操作放在自定义的transformer内,该transformer要实现ClassFileTransformer.transform() 主程序运行时:加载每个类前都会进入transform(),可以获取到它的加载器、类名、字节码buffer等

动态挂载(Attach):

动态挂载之后,类加载时都会执行transform,我们在此方法中通过 Instrumentation 增强目标类。

动态挂载前 已经加载的类,都未经历增强的处理。 可通过调用retransformClasses方法,让已加载的类重新加载, 重新加载时也会执行transform,我们在此方法中增强目标类。

在已经运行的JVM进程中,动态的插入。通过attach API调用,在sandbox中执行./sandbox -p pid(目标 jvm 进程 id)是使用的 attach api 方式进行 agent 的挂载

agentmain():对于agentmain也是一样的流程,但多一步 inst.retransformClasses() 的操作让JVM重新加载修改过的类的字节码,否则修改不会生效使用com.sun.tools.attach.VirtualMachine进行动态挂载Agent:

复制代码
java 复制代码
private void attachAgentToTargetJVM() throws Exception {

List<VirtualMachineDescriptor> virtualMachineDescriptors = VirtualMachine.list();

VirtualMachineDescriptor targetVM = null;

for (VirtualMachineDescriptor descriptor : virtualMachineDescriptors) {

if (descriptor.id().equals(configure.getPid())) {

targetVM = descriptor;

break;

}

}

if (targetVM == null) {

throw new IllegalArgumentException("could not find the target jvm by process id:" + configure.getPid());

}

VirtualMachine virtualMachine = null;

try {

virtualMachine = VirtualMachine.attach(targetVM);

virtualMachine.loadAgent("{agent}", "{params}");

} catch (Exception e) {

if (virtualMachine != null) {

virtualMachine.detach();

}

}

}

它的 addTransformer 给 Instrumentation 注册一个 transformer,transformer 是 ClassFileTransformer 接口的实例,这个接口就只有一个 transform 方法,调用 addTransformer 设置 transformer 以后,后续JVM 加载所有类之前都会被这个 transform 方法拦截,这个方法接收原类文件的字节数组,返回转换过的字节数组,在这个方法中可以做任意的类文件改写。

8.1.3 JavaAgent使用demo

使用premain的方式进行javaAgent的启动(不详细展示对java类的增强demo)

首先在一个maven项目中创建我们的核心demo-AgentDemo

复制代码
java 复制代码
package com.xhz.demo;

 

import java.lang.instrument.Instrumentation;

 

/**

* @version V1.0

* @Author : xiehuizhi

* @create 2023/12/20 14:33

*/

 

public class AgentDemo {

public static void premain(String agentArgs, Instrumentation instrumentation){

 

System.out.println(agentArgs+"============尊贵的xxxx===============");

 

}

public static void premain(String agentArgs){

System.out.println(agentArgs+"============尊贵的xxxx================");

}

}

 

 

创建完成之后可以手动在resources中创建MANIFEST.MF文件(也可以在pom中配置)

复制代码

Manifest-Version: 1.0

Premain-Class: com.xhz.demo.AgentDemo

Can-Redefine-Classes: true

Can-Retransform-Classes: true

pom配置:

复制代码
XML 复制代码
<plugins>

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-jar-plugin</artifactId>

<version>3.1.2</version>

<configuration>

<archive>

<manifestEntries>

<Premain-Class>club.throwable.permain.PermainAgent</Premain-Class>

<Can-Redefine-Classes>true</Can-Redefine-Classes>

<Can-Retransform-Classes>true</Can-Retransform-Classes>

</manifestEntries>

</archive>

</configuration>

</plugin>

</plugins>

然后要实现agent的功能,需要将你的项目打成jar包

打包命令:mvn clean package

然后新建一个可以启动的Main类

复制代码
java 复制代码
package Utils;

 

/**

* @version V1.0

* @Author : xiehuizhi

* @create 2023/12/20 14:29

*/

 

public class Main {

public static void main(String[] args) {

System.out.println("流量回放");

}

}

在springboot启动项增加启动参数

8.2 Sandbox启动与Agent挂载的流程

sandbox-spy: 这个包里的代码是一系列的模板代码,后期Sandbox会通过ASM框架将这些代码转化成字节码增加到业务代码的字节码当中,实现对业务代码的增强功能

但spy类为什么要被BootstrapClassLoader加载呢?我们不仅要增强业务代码,也要增强JDK里面的代码,将spy添加到BootstrapClassLoader的类路径,只要不破坏双亲委托机制,都能够被spy类增强,这是一种比较保险的做法。

8.3 JVM-SandBox实现字节码增强

一般的Spring AOP:实现业务切面,针对于JAVA后端应用也需要AOP

JVM-Sandbox的设计目的是实现一种在不重启、不侵入目标JVM应用情况下的AOP解决方案。

Jvm-SandBox字节码增强流程梳理:

8.3.1 增强过程

8.3.2 实现字节码增强逻辑

通过AsmMethods接口获取Sandbox自定义Spy类完成字节码增强

spy增强的静态方法

JavaAgent的本质就是字节码增强,这章主要介绍了Sandbox字节码增强功能的核心逻辑,包括增强逻辑、SandboxClassFileTransformer类形变器、Spy增强的静态方法。

相关推荐
魔镜魔镜_谁是世界上最漂亮的小仙女6 分钟前
java-集合
java·后端·程序员
真实的菜8 分钟前
消息队列高级特性与原理:解锁分布式系统的底层逻辑
java
若水不如远方9 分钟前
java范型
java
凌辰揽月12 分钟前
Web后端基础(基础知识)
java·开发语言·前端·数据库·学习·算法
lifallen18 分钟前
深入浅出 Arrays.sort(DualPivotQuicksort):如何结合快排、归并、堆排序和插入排序
java·开发语言·数据结构·算法·排序算法
长安不见20 分钟前
背景知识: 理解LimitLatch背后的AQS
java
小吕学编程22 分钟前
策略模式实战:Spring中动态选择商品处理策略的实现
java·开发语言·设计模式
weixin_4383354028 分钟前
Spring Boot实现接口时间戳鉴权
java·spring boot·后端
pan_junbiao1 小时前
Spring框架的设计模式
java·spring·设计模式
远方16091 小时前
0x-2-Oracle Linux 9上安装JDK配置环境变量
java·linux·oracle