• Java Instrumentation简介


    整理ibm.com,不完善

    插装&ASM

    介绍

    插桩技术是在保证目标程序原有逻辑完整的情况下,在特定的位置插入代码段,从而收集程序运行时的动态上下文信息

    目前基于插桩技术实现Java程序的动态交互安全监测已经有一些实现形式,如RASP,IAST。在Java中插桩通过Instrument以及字节码操作工具(如:ASM,Javassist,Byte Buddy等)实现

    相关知识

    Instrumentation简介

    Java SE 5 引入了静态Instument的概念,利用它我们可以构建一个独立于应用程序的代理程序(Agent),用力啊检测和协助运行在JVM上的程序,这样的特性实际上提供了一种虚拟机级别支持的AOP实现方式,如此无需对应用程序做升级改动,就能实现某些AOP的功能

    在Java SE 6 ,instrumentation包被赋予更大的功能包括:启动后的instrument,本地代码(native code)instument,以及动态改变classpath等

    Instrumentation基本功能和用法

    在 Instrumentation 的实现当中,存在一个 JVMTI 的代理程序,通过调用 JVMTI 当中 Java 类相关的函数来完成 Java 类的动态操作

    使用步骤:

    1. 编写premain函数

      • 编写Java类,包含以下两个方法

        public static void premain(String agentArgs, Instrumentation inst);   //[1]
        public static void premain(String agentArgs);  //[2]
        
      • 其中,[1]的优先级比[2]高,将会被优先([1] [2]同时存在时[2]将会被忽略)

      • 在premain函数中,开发者可以对类进行各种操作。java.lang.instrument.Instrumentation 是 instrument 包中定义的一个核心接口,集中了所有的功能方法

      • agentArgs时premain函数得到的程序参数,随同"- javaagent"一起传入

    2. jar文件打包

      • 将这个Java类打包成一个jar文件,同时在manifest属性中加入Primain-Class来指定步骤一中编写的有oremain的Java类
    3. 运行

      java -javaagent:jar 文件位置 [= 传入 primain的参数]
      

      建立一个Transformer类:

    import java.io.File; 
    import java.io.FileInputStream; 
    import java.io.IOException; 
    import java.io.InputStream; 
    import java.lang.instrument.ClassFileTransformer; 
    import java.lang.instrument.IllegalClassFormatException; 
    import java.security.ProtectionDomain; 
     
    class Transformer implements ClassFileTransformer { 
     
       public static final String classNumberReturns2 = "TransClass.class.2"; 
     
       public static byte[] getBytesFromFile(String fileName) { 
           try { 
               // precondition 
               File file = new File(fileName); 
               InputStream is = new FileInputStream(file); 
               long length = file.length(); 
               byte[] bytes = new byte[(int) length]; 
     
               // Read in the bytes 
               int offset = 0; 
               int numRead = 0; 
               while (offset <bytes.length
                       && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) { 
                   offset += numRead; 
               } 
     
               if (offset < bytes.length) { 
                   throw new IOException("Could not completely read file "
                           + file.getName()); 
               } 
               is.close(); 
               return bytes; 
           } catch (Exception e) { 
               System.out.println("error occurs in _ClassTransformer!"
                       + e.getClass().getName()); 
               return null; 
           } 
       } 
     
       public byte[] transform(ClassLoader l, String className, Class<?> c, 
               ProtectionDomain pd, byte[] b) throws IllegalClassFormatException { 
           if (!className.equals("TransClass")) { 
               return null; 
           } 
           return getBytesFromFile(classNumberReturns2); 
     
       } 
    }
    

    此类实现了ClassFileTransformer接口,其中getBytesFromFile方法根据二进制流字符,此处transform转化方法不进行详细举例

    public class Premain { 
           public static void premain(String agentArgs, Instrumentation inst)  
          		throws ClassNotFoundException, UnmodifiableClassException { 
           		inst.addTransformer(new Transformer()); 
           } 
    }
    

    可以看到,addTransformer方法并没有指定要转化哪一个类。转换发生在permain函数执行之后,main函数执行之前,这时状态一个类,transform方法就会执行一次

    所以在transform方法(Transformer类)中,使用className.equals("TransClass")判断当前的类是否需要转换

    Java SE 6 的新特性:虚拟机启动后的动态instrument

    在 Java SE 5 当中,开发者只能在 premain 当中进行处理

    Java SE 6 针对这种状况做出了改进,开发者可以在 main 函数开始执行以后,再启动自己的 Instrumentation 程序。

    类似于permain函数,开发者可以编写一个含有agentmain函数的Java类

    public static void agentmain (String agentArgs, Instrumentation inst);        //  [1] 
    public static void agentmain (String agentArgs);        //    [2]
    

    同样1的优先级高,agentArgs和Inst的用法与permain相同。开发者可以在agentmain中进行类的各种操作。其中agentArgs和Inst的用法跟premain相同

    与Premian-不同的是,agentmain需要在main函数运行后才启动

    此时,绑定的时机就成了问题。此时可以使用Java SE 6 中提供的Attach API

    Attach API

    Attach API不是Java的标准API,是Sun公司提供的一套拓展API,用来向目标JVM附着代理工具程序。可以用此方便的监控JVM,运行外加程序

    主要包含两部分:

    • VirtualMachine代表一个Java虚拟机,即程序需要监控的目标虚拟机,提供了 JVM 枚举,Attach 动作和 Detach 动作等等
    • VirtualMachineDescriptor 则是一个描述虚拟机的容器类,配合 VirtualMachine 类完成各种功能

    TransClass类和Transformer类的代码不变。含有 main 函数的 TestMainInJar 代码为:

    public class TestMainInJar { 
       public static void main(String[] args) throws InterruptedException { 
           System.out.println(new TransClass().getNumber()); 
           int count = 0; 
           while (true) { 
               Thread.sleep(500); 
               count++; 
               int number = new TransClass().getNumber(); 
               System.out.println(number); 
               if (3 == number || count >= 10) { 
                   break; 
               } 
           } 
       } 
    }
    

    含有 agentmain 的 AgentMain 类的代码为:

    import java.lang.instrument.ClassDefinition; 
    import java.lang.instrument.Instrumentation; 
    import java.lang.instrument.UnmodifiableClassException; 
     
    public class AgentMain { 
       public static void agentmain(String agentArgs, Instrumentation inst) 
               throws ClassNotFoundException, UnmodifiableClassException, 
               InterruptedException { 
           inst.addTransformer(new Transformer (), true); 
           inst.retransformClasses(TransClass.class); 
           System.out.println("Agent Main Done"); 
       } 
    }
    

    其中,retransformClasses 是 Java SE 6 里面的新方法,它跟 redefineClasses 一样,可以批量转换类定义,多用于 agentmain 场合

    Jar文件跟Permain那个例里面的Jar文件类似,也是把 main 和 agentmain 的类,TransClass,Transformer 等类放在一起,打包为TestInstrument1.jar。而 Jar 文件当中的 Manifest 文件为 :

    Manifest-Version: 1.0 
    Agent-Class: AgentMain
    

    为运行Attach API,可以写要给控制程序来模拟监控程序

    import com.sun.tools.attach.VirtualMachine; 
     import com.sun.tools.attach.VirtualMachineDescriptor; 
    ……
     // 一个运行 Attach API 的线程子类
     static class AttachThread extends Thread { 
             
     private final List<VirtualMachineDescriptor> listBefore; 
     
            private final String jar; 
     
            AttachThread(String attachJar, List<VirtualMachineDescriptor> vms) { 
                listBefore = vms;  // 记录程序启动时的 VM 集合
                jar = attachJar; 
            } 
     
            public void run() { 
                VirtualMachine vm = null; 
                List<VirtualMachineDescriptor> listAfter = null; 
                try { 
                    int count = 0; 
                    while (true) { 
                        listAfter = VirtualMachine.list(); 
                        for (VirtualMachineDescriptor vmd : listAfter) { 
                            if (!listBefore.contains(vmd)) { 
     // 如果 VM 有增加,我们就认为是被监控的 VM 启动了
     // 这时,我们开始监控这个 VM 
                                vm = VirtualMachine.attach(vmd); 
                                break; 
                            } 
                        } 
                        Thread.sleep(500); 
                        count++; 
                        if (null != vm || count >= 10) { 
                            break; 
                        } 
                    } 
                    vm.loadAgent(jar); 
                    vm.detach(); 
                } catch (Exception e) { 
                     ignore 
                } 
            } 
        } 
    ……
     public static void main(String[] args) throws InterruptedException {    
         new AttachThread("TestInstrument1.jar", VirtualMachine.list()).start(); 
     
     }
    

    运行时,可以首先运行上面这个启动新线程的main函数,然后在5s内(仅模拟简单JVM监控过程),启动方式如下

    java – javaagent:TestInstrument2.jar – cp TestInstrument2.jar TestMainInJar
    

    程序会首先在屏幕上打印出1,然后打印出2,表示agentmain已经被Attach API 成功附着到JVM上了,代理程序生效。

    Java Instrument工作原理

    javaagent

    00218c2023a2ba140887543f88a4fd99

  • 相关阅读:
    uploadify控件在QQ、TT、firefox浏览器中不工作以及在updatecontrol中不工作的解决办法
    记202235日钓鱼 那个人
    Subtask Gated Networks for NonIntrusive Load Monitoring
    C#反射的应用
    activiti7实现流程撤回的两种思路
    antd pro V5从服务端请求菜单
    mysql复制一个表的数据到已存在的表中(可夸数据库实例)
    elasticsearch索引、文档、映射等概念
    vue图片查看(放大、缩小、旋转)
    spring事务传播机制之《REQUIRED》
  • 原文地址:https://www.cnblogs.com/ginko/p/14439613.html
Copyright © 2020-2023  润新知