Java 动态字节码手艺_玖富娱乐主管发布


玖富娱乐是一家为代理招商,直属主管信息发布为主的资讯网站,同时也兼顾玖富娱乐代理注册登录地址。

对 Debug 的猎奇

初学 Java 时,我对 IDEA 的 Debug 异常猎奇,不止是它能检察断点的上下文情况,更奇异的是我可以或许在断点处运用它的 Evaluate 功用直接实行某些敕令,举行一些盘算或转变以后变量。

刚最先语法不熟常常写错代码,从新打包布置一次代码耗时很长,我就直接面向 Debug 开辟。在要编写的要领最先处打一个断点,在 Evaluate 框内一次次地实行要领函数不停地调解代码,没题目后再将代码复制出来放到 IDEA 里,再举行下一个要领的编写,如许就跟写 PHP 相似的诠释性言语一样,写完即实行,异常轻易。

但 Java 是静态言语,运转之前是要先举行编译的,岂非我写的这些代码是被及时编译又”注入”到我正在 Debug 的效劳里了吗?

跟着对 Java 的越发熟习,我也相识了反射、字节码等手艺,直到前些天的周会分享,有位同事分享了 Btrace 的运用和完成,提到了 Java 的 ASM 框架和 JVM TI 接口。 Btrace 修正代码能力的完成与 Debug 的 Evaluate 有许多相似之处,这大大吸收了我。分享就像一个引子,从中学到的器械只是外相,要相识它照样要本身研讨。因而本身检察材料并写代码进修了下其详细完成。

转载随便,文章会延续订正,请说明泉源地点:https://zhenbianshu.github.io 。

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”。

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

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

进口类,也是署理的 Agent-Class。

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

运用 tools.jar 里要领将 agent 动态加载到目的 JVM 的类。

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"); } } 

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

小结

控制了字节码的动态修正手艺后,再回头看 Btrace 的道理就更清楚了,轻微探索一下我们也可以或许完成一个简版的。别的许多大牛完成的种种 Java 机能剖析对象的手艺栈也不过云云,相识了这些,将来我们也可以或许写出合适本身的对象,最少能对他人的对象举行修正~

不得不说 Java 的生态真的异常繁华,当真是博学多才,查阅一个模块的材料时能总引出一大堆新的观点,永久有学不完的新器械。

关于本文有甚么疑问可以或许在下面留言交换,若是您以为本文对您有资助,迎接存眷我的 微博 或 GitHub 。您也可以或许在我的 博客REPO 右上角点击 Watch 并挑选 Releases only 项来 定阅 我的博客,有新文章发布会第一时间关照您。

参考:

教你用Java字节码做点风趣的事

Java Instrument道理

Java Platform Debugger Architecture Structure Overview

-玖富娱乐是一家为代理招商,直属主管信息发布为主的资讯网站,同时也兼顾玖富娱乐代理注册登录地址。