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增强的静态方法。

相关推荐
.格子衫.1 天前
Spring Boot 原理篇
java·spring boot·后端
多云几多1 天前
Yudao单体项目 springboot Admin安全验证开启
java·spring boot·spring·springbootadmin
Jabes.yang1 天前
Java求职面试实战:从Spring Boot到微服务架构的技术探讨
java·数据库·spring boot·微服务·面试·消息队列·互联网大厂
聪明的笨猪猪1 天前
Java Redis “高可用 — 主从复制”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
兮动人1 天前
Spring Bean耗时分析工具
java·后端·spring·bean耗时分析工具
MESSIR221 天前
Spring IOC(控制反转)中常用注解
java·spring
摇滚侠1 天前
Spring Boot 3零基础教程,Demo小结,笔记04
java·spring boot·笔记
笨手笨脚の1 天前
设计模式-迭代器模式
java·设计模式·迭代器模式·行为型设计模式
spencer_tseng1 天前
Eclipse 4.7 ADT (Android Development Tools For Eclipse)
android·java·eclipse
聪明的笨猪猪1 天前
Java Spring “AOP” 面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试