IDEA 的 debug 怎么实现?出于这个好奇心,我越挖越深!
码农突围
共 14181字,需浏览 29分钟
·
2021-05-15 14:41
点击上方“码农突围”,马上关注 这里是码农充电第一站,回复“666”,获取一份专属大礼包 真爱,请设置“星标”或点个“在看
来源 | https://zhenbianshu.github.io/
对 Debug 的好奇
ASM
动态生成字节码
ASM 框架
ASM Bytecode Outline
,在要查看的类文件里右键选择 Show bytecode Outline
即可以右侧的工具栏查看我们要生成的字节码。对照着示例,我们就可以很轻松地写出操作字节码的 Java 代码了。ASMified
标签栏,我们甚至可以直接获取到 ASM 的使用代码。常用方法
visitMethod()/visitAnnotation()
等方法,用以定义对类结构(如方法、字段、注解)的访问方法。Instrument
介绍
instrument
。使用
ClassFileTransformer
接口定义一个类文件转换器。它唯一的一个 transform()
方法会在类文件被加载时调用,在 transform 方法里,我们可以对传入的二进制字节码进行改写或替换,生成新的字节码数组后返回,JVM 会使用 transform 方法返回的字节码数据进行类的加载。JVM TI
介绍
Agent
-agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:3333
,而 -agentlib 选项就指定了我们要加载的 Java Agent,jdwp 是 agent 的名字,在 linux 系统中,我们可以在 jre 目录下找到 jdwp.so 库文件。jdi->jdwp->jvmti
,我们通过 JDI 接口发送调试指令,而 jdwp 就相当于一个通道,帮我们翻译 JDI 指令到 JVM TI,最底层的 JVM TI 最终实现对 JVM 的操作。使用
premain()
或 agentmain()
方法来实现。而要实现 1.6 以上的动态 instrument 功能,实现 agentmain 方法即可。Instrumentation.retransformClasses()
方法实现对目标类的重定义。VirtualMachine
类提供了 attach 一个本地 JVM 的功能,它需要我们传入一个本地 JVM 的 pid, tools.jar 可以在 jre 目录下找到。agent生成
MANIFEST.MF
文件的一些参数,允许我们重新定义类。如果你的 agent 实现还需要引用一些其他类库时,还需要将这些类库都打包到此 jar 包中,下面是我的 pom 文件配置。<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Agent-Class>asm.TestAgent</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
<Manifest-Version>1.0</Manifest-Version>
<Permissions>all-permissions</Permissions>
</manifestEntries>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</build>
mvn assembly:assembl
命令生成 jar-with-dependencies 作为 agent。代码实现
被修改的类
public class TransformTarget {
public static void main(String[] args) {
while (true) {
try {
Thread.sleep(3000L);
} catch (Exception e) {
break;
}
printSomething();
}
}
public static void printSomething() {
System.out.println("hello");
}
}
Agent
public class TestAgent {
public static void agentmain(String args, Instrumentation inst) {
inst.addTransformer(new TestTransformer(), true);
try {
inst.retransformClasses(TransformTarget.class);
System.out.println("Agent Load Done.");
} catch (Exception e) {
System.out.println("agent load failed!");
}
}
}
public class TestTransformer implements ClassFileTransformer {
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
System.out.println("Transforming " + className);
ClassReader reader = new ClassReader(classfileBuffer);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
ClassVisitor classVisitor = new TestClassVisitor(Opcodes.ASM5, classWriter);
reader.accept(classVisitor, ClassReader.SKIP_DEBUG);
return classWriter.toByteArray();
}
class TestClassVisitor extends ClassVisitor implements Opcodes {
TestClassVisitor(int api, ClassVisitor classVisitor) {
super(api, classVisitor);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if (name.equals("printSomething")) {
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(19, l0);
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("bytecode replaced!");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLineNumber(20, l1);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(2, 0);
mv.visitEnd();
TransformTarget.printSomething();
}
return mv;
}
}
}
Attacher
public class Attacher {
public static void main(String[] args) throws AttachNotSupportedException, IOException, AgentLoadException, AgentInitializationException {
VirtualMachine vm = VirtualMachine.attach("34242"); // 目标 JVM pid
vm.loadAgent("/path/to/agent.jar");
}
}
小结
- END - 最近热文
• 再见 Win10!下一代操作系统要来了! • 女友回老家了!没吊事,手把手带你搭建一台服务器! • 尼玛,Github上最邪恶的开源项目了!未满18或者女孩子勿进哦~ • 永别了,91网站!宣布永久关闭
评论