首页 » PHP教程 » phpdebugbuild技巧_IDEA 的 debug 怎么实现出于这个好奇心我越挖越深

phpdebugbuild技巧_IDEA 的 debug 怎么实现出于这个好奇心我越挖越深

访客 2024-11-07 0

扫一扫用手机浏览

文章目录 [+]

刚开始语法不熟常常写错代码,重新打包支配一次代码耗时很长,我就直接面向 Debug 开拓。
在要编写的方法开始处打一个断点,在 Evaluate 框内一次次地实行方法函数一直地调度代码,没问题后再将代码复制出来放到 IDEA 里,再进行下一个方法的编写,这样就跟写 PHP 类似的阐明性措辞一样,写完即实行,非常方便。

但 Java 是静态措辞,运行之前是要前辈行编译的,难道我写的这些代码是被实时编译又”注入”到我正在 Debug 的做事里了吗?

phpdebugbuild技巧_IDEA 的 debug 怎么实现出于这个好奇心我越挖越深

随着对 Java 的愈加熟习,我也理解了反射、字节码等技能,直到前些天的周会分享,有位同事分享了 Btrace 的利用和实现,提到了 Java 的 ASM 框架和 JVM TI 接口。
Btrace 修正代码能力的实现与 Debug 的 Evaluate 有很多相似之处,这大大吸引了我。

phpdebugbuild技巧_IDEA 的 debug 怎么实现出于这个好奇心我越挖越深
(图片来自网络侵删)

分享就像一个引子,从中学到的东西只是皮毛,要理解它还是要自己研究。
于是自己查看资料并写代码学习了下其详细实现。

ASM

实现 Evaluate 要办理的第一个问题便是怎么改变原有代码的行为,它的实现在 Java 里被称为动态字节码技能。

动态天生字节码

我们知道,我们编写的 Java 代码都是要被编译成字节码后才能放到 JVM 里实行的,而字节码一旦被加载到虚拟机中,就可以被阐明实行。

字节码文件(.class)便是普通的二进制文件,它是通过 Java 编译器天生的。
而只假如文件就可以被改变,如果我们用特定的规则解析了原有的字节码文件,对它进行修正或者干脆重新定义,这不就可以改变代码行为了么。

Java 生态里有很多可以动态天生字节码的技能,像 BCEL、Javassist、ASM、CGLib 等,它们各有自己的上风。
有的利用繁芜却功能强大、有的大略确也性能些差。

ASM 框架

ASM 是它们中最强大的一个,利用它可以动态修正类、方法,乃至可以重新定义类,连 CGLib 底层都是用 ASM 实现的。

当然,它的利用门槛也很高,利用它须要对 Java 的字节码文件有所理解,熟习 JVM 的编译指令。
虽然我对 JVM 的字节码语法不熟,但有大神开拓了可以在 IDEA 里查看字节码的插件:ASM Bytecode Outline ,在要查看的类文件里右键选择 Show bytecode Outline 即可以右侧的工具栏查看我们要天生的字节码。
对照着示例,我们就可以很轻松地写出操作字节码的 Java 代码了。

而切到 ASMified 标签栏,我们乃至可以直接获取到 ASM 的利用代码。

常用方法

在 ASM 的代码实现里,最明显的便是访问者模式,ASM 将对代码的读取和操作都包装成一个访问者,在解析 JVM 加载到的字节码时调用。

ClassReader 是 ASM 代码的入口,通过它解析二进制字节码,实例化时它时,我们须要传入一个 ClassVisitor,在这个 Visitor 里,我们可以实现 visitMethod()/visitAnnotation() 等方法,用以定义对类构造(如方法、字段、表明)的访问方法。

而 ClassWriter 接口继续了 ClassVisitor 接口,我们在实例化类访问器时,将 ClassWriter “注入” 到里面,以实现对类写入的声明。

Instrument先容

字节码是修正完了,可是 JVM 在实行时会利用自己的类加载器加载字节码文件,加载后并不会理会我们做出的修正,要想实现对现有类的修正,我们还须要搭配 Java 的另一个库 instrument。

instrument 是 JVM 供应的一个可以修正已加载类文件的类库。
1.6以前,instrument 只能在 JVM 刚启动开始加载类时生效,之后,instrument 更是支持了在运行时对类定义的修正。

利用

要利用 instrument 的类修正功能,我们须要实现它的 ClassFileTransformer 接口定义一个类文件转换器。
它唯一的一个 transform() 方法会在类文件被加载时调用,在 transform 方法里,我们可以对传入的二进制字节码进行改写或更换,天生新的字节码数组后返回,JVM 会利用 transform 方法返回的字节码数据进行类的加载。

JVM TI

定义完了字节码的修正和重定义方法,但我们怎么才能让 JVM 能够调用我们供应的类转换器呢?这里又要先容到 JVM TI 了。

先容

JVM TI(JVM Tool Interface)JVM 工具接口是 JVM 供应的一个非常强大的对 JVM 操作的工具接口,通过这个接口,我们可以实现对 JVM 多种组件的操作,从JVMTM Tool Interface 这里我们认识到 JVM TI 的强大,它包括了对虚拟机堆内存、类、线程等各个方面的管理接口。

JVM TI 通过事宜机制,通过接口注册各种事宜勾子,在 JVM 事宜触发时同时触发预定义的勾子,以实现对各个 JVM 事宜的感知和反应。

Agent

Agent 是 JVM TI 实现的一种办法。
我们在编译 C 项目里链接静态库,将静态库的功能注入到项目里,从而才可以在项目里引用库里的函数。
我们可以将 agent 类比为 C 里的静态库,我们也可以用 C 或 C++ 来实现,将其编译为 dll 或 so 文件,在启动 JVM 时启动。

这时再来思考 Debug 的实现,我们在启动被 Debug 的 JVM 时,必须添加参数 -agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:3333,而 -agentlib 选项就指定了我们要加载的 Java Agent,jdwp 是 agent 的名字,在 linux 系统中,我们可以在 jre 目录下找到 jdwp.so 库文件。

Java 的调试体系 jdpa 组成,从高到低分别为 jdi->jdwp->jvmti,我们通过 JDI 接口发送调试指令,而 jdwp 就相称于一个通道,帮我们翻译 JDI 指令到 JVM TI,最底层的 JVM TI 终极实现对 JVM 的操作。

利用

JVM TI 的 agent 利用很大略,在启动 agent 时添加 -agent 参数指定我们要加载的 agent jar包即可。

而要实当代码的修正,我们须要实现一个 instrument agent,它可以通过在一个类里添加 premain() 或 agentmain() 方法来实现。
而要实现 1.6 以上的动态 instrument 功能,实现 agentmain 方法即可。

在 agentmain 方法里,我们调用 Instrumentation.retransformClasses() 方法实现对目标类的重定义。

其余往一个正在运行的 JVM 里动态添加 agent,还须要用到 JVM 的 attach 功能,Sun 公司的 tools.jar 包里包含的 VirtualMachine 类供应了 attach 一个本地 JVM 的功能,它须要我们传入一个本地 JVM 的 pid, tools.jar 可以在 jre 目录下找到。

agent天生

其余,我们还须要把稳 agent 的打包,它须要指定一个 Agent-Class 参数指定我们的包括 agentmain 方法的类,可以算是指定入口类吧。

此外,还须要配置 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。

代码实现

我在测试时写了一个用以上技能实现了一个大略的字节码动态修正的 Demo。

被修正的类

TransformTarget 是要被修正的目标类,正常实行时,它会三秒输出一次 “hello”。

publicclassTransformTarget{publicstaticvoidmain(String[]args){while(true){try{Thread.sleep(3000L);}catch(Exceptione){break;}printSomething();}}publicstaticvoidprintSomething(){System.out.println("hello");}}Agent

Agent 是实行修正类的主体,它利用 ASM 修正 TransformTarget 类的方法,并利用 instrument 包将修正提交给 JVM。

入口类,也是代理的 Agent-Class。

publicclassTestAgent{publicstaticvoidagentmain(Stringargs,Instrumentationinst){inst.addTransformer(newTestTransformer(),true);try{inst.retransformClasses(TransformTarget.class);System.out.println("AgentLoadDone.");}catch(Exceptione){System.out.println("agentloadfailed!");}}}

实行字节码修正和转换的类。

publicclassTestTransformerimplementsClassFileTransformer{publicbyte[]transform(ClassLoaderloader,StringclassName,Class<?>classBeingRedefined,ProtectionDomainprotectionDomain,byte[]classfileBuffer)throwsIllegalClassFormatException{System.out.println("Transforming"+className);ClassReaderreader=newClassReader(classfileBuffer);ClassWriterclassWriter=newClassWriter(ClassWriter.COMPUTE_FRAMES);ClassVisitorclassVisitor=newTestClassVisitor(Opcodes.ASM5,classWriter);reader.accept(classVisitor,ClassReader.SKIP_DEBUG);returnclassWriter.toByteArray();}classTestClassVisitorextendsClassVisitorimplementsOpcodes{TestClassVisitor(intapi,ClassVisitorclassVisitor){super(api,classVisitor);}@OverridepublicMethodVisitorvisitMethod(intaccess,Stringname,Stringdesc,Stringsignature,String[]exceptions){MethodVisitormv=super.visitMethod(access,name,desc,signature,exceptions);if(name.equals("printSomething")){mv.visitCode();Labell0=newLabel();mv.visitLabel(l0);mv.visitLineNumber(19,l0);mv.visitFieldInsn(Opcodes.GETSTATIC,"java/lang/System","out","Ljava/io/PrintStream;");mv.visitLdcInsn("bytecodereplaced!");mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"java/io/PrintStream","println","(Ljava/lang/String;)V",false);Labell1=newLabel();mv.visitLabel(l1);mv.visitLineNumber(20,l1);mv.visitInsn(Opcodes.RETURN);mv.visitMaxs(2,0);mv.visitEnd();TransformTarget.printSomething();}returnmv;}}}Attacher

利用 tools.jar 里方法将 agent 动态加载到目标 JVM 的类。

publicclassAttacher{publicstaticvoidmain(String[]args)throwsAttachNotSupportedException,IOException,AgentLoadException,AgentInitializationException{VirtualMachinevm=VirtualMachine.attach("34242");//目标JVMpidvm.loadAgent("/path/to/agent.jar");}}

这样,先启动 TransformTarget 类,获取到 pid 后将其传入 Attacher 里,并指定 agent jar,将 agent attach 到 TransformTarget 中,原来输出的 “hello” 就变成我们想要修正的 “bytecode replaced!” 了。

小结

节制了字节码的动态修正技能后,再转头看 Btrace 的事理就更清晰了,轻微摸索一下我们也可以实现一个简版的。
其余很多大牛实现的各种 Java 性能剖析工具的技能栈也不外如此,理解了这些,未来我们也可以写出适宜自己的工具,至少能对别人的工具进行修正~

不得不说 Java 的生态真的非常繁荣,当真是博大精湛,查阅一个模块的资料时能总引出一大堆新的观点,永久有学不完的新东西。

标签:

相关文章

执业药师试卷代码解码药师职业发展之路

执业药师在药品质量管理、用药安全等方面发挥着越来越重要的作用。而执业药师考试,作为进入药师行业的重要门槛,其试卷代码更是成为了药师...

PHP教程 2025-02-18 阅读1 评论0

心灵代码主题曲唤醒灵魂深处的共鸣

音乐,作为一种独特的艺术形式,自古以来就承载着人类情感的表达与传递。心灵代码主题曲,以其独特的旋律和歌词,唤醒了无数人的灵魂深处,...

PHP教程 2025-02-18 阅读0 评论0

探寻福建各市车牌代码背后的文化内涵

福建省,地处我国东南沿海,拥有悠久的历史和丰富的文化底蕴。在这片充满魅力的土地上,诞生了许多具有代表性的城市,每个城市都有自己独特...

PHP教程 2025-02-18 阅读1 评论0

探寻河北唐山历史与现代交融的城市之光

河北省唐山市,一座地处渤海之滨,拥有悠久历史和独特文化的城市。这里既是古丝绸之路的起点,也是中国近代工业的发源地。如今,唐山正以崭...

PHP教程 2025-02-18 阅读1 评论0