-
使用arthas,通过jad、mc、retransformer可以将一个类中的某个方法进行改造,变成需要的方法,并且可以通过vmtool进行调用 这里可以通过arthas的官方文档操作,对当前类有侵入。 arthas.aliyun.com/doc/retrans…
-
不使用arthas,而是用自己的java代码该如何实现呢? 实际上arthas也是使用了JVMTI这一类工具(大概是这个名吧) 我们自己也可以编写这样的工具,在测试环境里面随便玩,生产环境遇到问题应急也可以用用(大概 一步一步编写,先通过常规手段来进行类加载。
(1)需要自己写一个classloader,可以加载自己的类,这个类需要在当前运行的jvm之中
package test;
/**
* 为了多次载入执行类而加入的加载器
* 把defineClass方法开放出来,只有外部显式调用的时候才会使用到loadByte方法
* 由虚拟机调用时,仍然按照原有的双亲委派规则使用loadClass方法进行类加载
*
* @author zzm
*/
public class HotSwapClassLoader extends ClassLoader {
public HotSwapClassLoader() {
super(HotSwapClassLoader.class.getClassLoader());
}
public Class loadByte(byte[] classByte) {
return defineClass(null, classByte, 0, classByte.length);
}
}
(2)通过编写agent代码来创建agent jar,通过这个jar可以attach到对应的jvm之中
package agent;
import java.lang.instrument.Instrumentation;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class MyAgent {
public static void agentmain(String agentArgs, Instrumentation inst) {
try {
System.out.println(agentArgs);
String[] args = agentArgs.split(",");
String loadClassPath = args[0];
String className = args[1];
String methodName = args[2];
// 加载你的类
Path path = Paths.get(loadClassPath);
byte[] bytes = Files.readAllBytes(path);
Class<?> myClass = Class.forName("test.HotSwapClassLoader");
Object hotSwapLoader = myClass.newInstance();
myClass.getMethod("loadByte", byte[].class)
.invoke(hotSwapLoader, new Object[]{bytes});
// 调用方法
Class instanceCls = Class.forName(className);
Object obj = instanceCls.newInstance();
instanceCls.getMethod(methodName).invoke(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}
(3)编写对应的attach jar包,包括你想要添加的class,然后是想要反射执行的方法
package test;
import com.sun.tools.attach.VirtualMachine;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.StringJoiner;
public class AgentAttacher {
public static void main(String[] args) throws Exception {
String jvmPid = "12345"; // JVM的PID
String agentJar = "java-agent-1.0-SNAPSHOT.jar";
String[] agentArgs = new String[3];
String classPath = "Retransform2.class";
byte[] bytes = Files.readAllBytes(Paths.get(classPath));
agentArgs[0] = classPath;
agentArgs[1] = "test.Retransform2";
agentArgs[2] = "testMyMethod";
StringJoiner joiner = new StringJoiner(",");
for (String arg : agentArgs) {
joiner.add(arg);
}
System.out.println(joiner.toString());
VirtualMachine vm = VirtualMachine.attach(jvmPid);
vm.loadAgent(agentJar, joiner.toString());
vm.detach();
}
}
(4)添加自己喜欢的功能吧,比如让输出可以打印在某个文件之中。最关键的依然是classloader这块。
- 不要通过内置的classloader,而是通过agent来植入classloader
直接把上面的hotSwapClassLoader挪到agent代码中,直接在agent调用就可以植入了
因为很多远程工具都是不需要对原生代码做任何改动的,那么可以认为它们实际上是有着自己的classloader的,这些classloader会将必要的类attach到对应的jvm之中。
实际上可以说,attach模式可以执行任何java代码。
- 可重复加载的类
在agent末尾处对类进行回收,通过置null以及System.gc可以让新的类重新加载。