运行时修改字节码

需求:在运行时动态修改某个类的字节码文件,不重启服务器。

方案:asm/javaassist + agent+Instrumentation

1.字节码修改框架

有很多第三方的字节码修改框架,由于前期接触了apm 产品pinpoint,所以决定使用asm/javaassist 框架,相对而言javaasist更为简单,但是效率较低。

ASM框架概述:

ASM有两种处理字节码的API分别是:


The ASM library provides two APIs for generating and transforming compiled
classes: the core API provides an event based representation of classes, while
the tree API provides an object based representation.

可以类比解析xml
These two APIs can be compared to the Simple API for XML (SAX) and
Document Object Model (DOM) APIs for XML documents: the event based
API is similar to SAX, while the object based API is similar to DOM. The
object based API is built on top of the event based one, like DOM can be
provided on top of SAX.


ASM字节码修改demo:
github:https://github.com/chengbingh...

ASM 十分底层,比如我们要生成一个包含几个熟悉和一个方法的接口,其代码如下:

    public static void main(String[] args) throws IOException {
        ClassWriter cw = new ClassWriter(0);
        cw.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT + Opcodes.ACC_INTERFACE,
                "com/hcb/asm/generate1/Comparable", null, "java/lang/Object",
                new String[]{"com/hcb/asm/generate123"});
        cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, "LESS", "I",
                null, new Integer(0)).visitEnd();
        cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, "EQUAL", "I",
                null, new Integer(1)).visitEnd();
        cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, "GREATER", "I",
                null, new Integer(2)).visitEnd();
        cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT, "compareTo",
                "(Ljava/lang/Object;)I", null, null).visitEnd();
        cw.visitEnd();

        byte[] b = null;
        b = cw.toByteArray();
        //将上述生成的byts 写入一个XX.class 文件中,再通过一个反编译软件就可以看到生成的类。
        File file = new File("Comparable1.class");
        FileOutputStream fos = new FileOutputStream(file);
        fos.write(b);
        fos.close();
    }

如果操作不当会产生一些奇怪的字节码,比如下面的这个类,其方法和属性都重复了:

clipboard.png

javaasist框架demo:
github:https://github.com/chengbingh...

clipboard.png

上述demo展示了如何使用javaassist修改字节码.

**

2. agent说明

java 提供了agent可以用来修改字节码,有两个修改时机,一个是class文件加载进虚拟机时,一个是在runtime。
可以通过参数:-javaagent 指定agent jar 包位置,其实在jdk 中有一个示例jar,以jdk1.8为例,在JRE_HOEM/lib/management-agent.jar

这是一个空的jar包,但是其中有manifest.mf的写法。
clipboard.png

clipboard.png

Premain-Class: 对应是在加载进jvm时。
我们以pingpoint1.7.1为参考:
pinpoint的agent 指定的一个启动参数是:
-javaagent:C:/kingdee/tomcat/apache-tomcat-8.0.48_ppagent/agent-hcb/pinpoint-bootstrap-1.7.1.jar

clipboard.png

我们注意到premain-class指定的class有一个方法premain,这个方法即入口函数

clipboard.png

Agent-Class:对应运行时。

3.运行时修改字节码案例

github:地址:
修改工程:https://github.com/chengbingh...
测试工程:https://github.com/chengbingh...

clipboard.png

  • UpdateClazz的main函数中根据某虚拟机的pid将agent 的jar包load到agent.
  • agent jar 包的manifest 文件中指定了agentMain方法所在的类

clipboard.png

  • AgentMain 的agentMain方法中调用Transformer修改修改字节码,案例中使用javaasist修改字节码。

测试步骤是:

  • 访问启动tomcat访问对于servlet

clipboard.png

  • UpdateClazz修改字节码(可以用jps命令获取pid)
  • 再次访问

clipboard.png

发现已经修改

相关微信公众号:

clipboard.png


dhxx
37 声望19 粉丝

中间件相关