当前位置:   article > 正文

Java Agent介绍及其使用_agent java

agent java

一、简介

        Java Agent技术,也被称为Java代理、Java探针,它允许程序员利⽤其构建⼀个独⽴于应⽤程序的代理程序。

        Java Agent 本质上就是一个 jar 包,对于普通的Jar包,通过指定类的 main 函数进行启动,但是 Java Agent 并不能单独启动,必须依附在一个 Java 应用程序运行。使用Java Agent可以实现在Java程序运行的过程中对其进行修改。

        Java Agent的jar包主要包含两个部分:实现代码与配置文件。配置文件名为MANIFEST.MF,放在META - INF文件夹下,包含下列配置项:

        Manifest-Version: 版本号
        Created-By: 创作者
        Premain-Class :包含 premain 方法的类
        Agent-Class :包含 agentmain 方法的类
        Can-Redefine-Classes :true表示能重定义此代理所需的类,默认值为 false
        Can-Retransform-Classes :true 表示能重转换此代理所需的类,默认值为 false 

        Java Agent的入口是premain或agentmain,具体选用哪一个以及其中的内容需要根据应用场景决定。

二、使用场景

        Java Agent 技术有以下主要功能:

  • 在加载java文件前拦截字节码并做修改
  • 在运行期间变更已加载的类的字节码
  •  获取所有已经被加载过的类
  • 获取所有已经被初始化过了的类
  • 获取某个对象的大小

        这些功能使得Java Agent在作为一个独立于Java应用程序的代理程序的同时,可以协助监测、运行甚至替换 JVM 上的程序。Agent的应用十分广泛,可用于实现Java IDE的调试功能、热部署功能、线上诊断⼯具和性能分析⼯具。例如,百度网络攻击防护工具OpenRASP中就使用了Java Agent来对敏感函数进行插桩,以此实现攻击检测。

三、使用方法

        Java Agent分为两种:静态agent与动态agent,这两种方式的启动方法与运行机制是不同的。

1. 静态agent

        这种方式是使用premain作为agent的入口方法,以vm参数的方式载入,在Java程序的 main 方法执行之前执行。要使用agent,需要在premain中实现我们想要的功能。agent技术使用JVMTI提供的接口来实现对应的功能,下面是一些官方的Instrumentation接口:

  1. void addTransformer(ClassFileTransformer transformer, boolean canRetransform)
  2. //注册ClassFileTransformer实例,注册多个会按照注册顺序进行调用。所有的类被加载完毕之后会调用ClassFileTransformer实例,相当于它们通过了redefineClasses方法进行重定义。布尔值参数canRetransform决定这里被重定义的类是否能够通过retransformClasses方法进行回滚。
  3. void addTransformer(ClassFileTransformer transformer)
  4. //相当于addTransformer(transformer, false),也就是通过ClassFileTransformer实例重定义的类不能进行回滚。
  5. boolean removeTransformer(ClassFileTransformer transformer)
  6. //移除(反注册)ClassFileTransformer实例。
  7. void retransformClasses(Class<?>... classes)
  8. //已加载类进行重新转换的方法,重新转换的类会被回调到ClassFileTransformer的列表中进行处理。
  9. void appendToBootstrapClassLoaderSearch(JarFile jarfile)
  10. //将某个jar加入到Bootstrap Classpath里优先其他jar被加载。
  11. void appendToSystemClassLoaderSearch(JarFile jarfile)
  12. //将某个jar加入到Classpath里供AppClassloard去加载。
  13. Class[] getAllLoadedClasses()
  14. //获取所有已经被加载的类。
  15. Class[] getInitiatedClasses(ClassLoader loader)
  16. //获取所有已经被初始化过了的类。
  17. long getObjectSize(Object objectToSize)
  18. //获取某个对象的(字节)大小,注意嵌套对象或者对象中的属性引用需要另外单独计算。
  19. boolean isModifiableClass(Class<?> theClass)
  20. //判断对应类是否被修改过。
  21. boolean isNativeMethodPrefixSupported()
  22. //是否支持设置native方法的前缀。
  23. boolean isRedefineClassesSupported()
  24. //返回当前JVM配置是否支持重定义类(修改类的字节码)的特性。
  25. boolean isRetransformClassesSupported()
  26. //返回当前JVM配置是否支持类重新转换的特性。
  27. void redefineClasses(ClassDefinition... definitions)
  28. //重定义类,也就是对已经加载的类进行重定义,ClassDefinition类型的入参包括了对应的类型Class<?>对象和字节码文件对应的字节数组。
  29. void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix)
  30. //设置某些native方法的前缀,主要在找native方法的时候做规则匹配。

        使用静态agent的步骤如下:

        1)编写premain函数,该函数应包含下面两个方法的其中之一:

  1. public static void premain(String agentArgs, Instrumentation inst);
  2. public static void premain(String agentArgs);

        如果两个方法都被实现了,那么带Instrumentation参数的会被优先调用。agentArgs是premain函数得到的程序参数,通过命令行传入。

        例如,OpenRASP中的premin函数:

  1. /**
  2. * 启动时加载的agent入口方法
  3. *
  4. * @param agentArg 启动参数
  5. * @param inst {@link Instrumentation}
  6. */
  7. public static void premain(String agentArg, Instrumentation inst) {
  8. init(START_MODE_NORMAL, START_ACTION_INSTALL, inst);
  9. }

        2)定义一个MANIFEST.MF文件,其中必须包含Premain-Class选项。例如OpenRASP工具中rasp.jar包内的MANIFEST.MF文件如下:

  1. Manifest-Version: 1.0
  2. Build-Time: 2022-03-11T11:55:29Z
  3. Last-Commit-User-Email: lixinkai@baidu.com
  4. Built-By: root
  5. Premain-Class: com.baidu.openrasp.Agent
  6. Created-By: Apache Maven 3.2.3
  7. Git-Branch: 1.3.7
  8. Git-Commit: f264cc6
  9. Last-Commit-User-Name: lixinkai
  10. Build-Jdk: 1.6.0_45
  11. Project-Version: 1.3.7
  12. Agent-Class: com.baidu.openrasp.Agent
  13. Can-Redefine-Classes: true
  14. Main-Class: com.baidu.openrasp.Agent
  15. Can-Retransform-Classes: true
  16. Archiver-Version: Plexus Archiver

        3)将包含premain的类与MANIFEST.MF文件打包成一个 jar 包

        4)在启动java程序时添加启动参数 -javaagent:[path],其中的path为对应的agent的jar包路径。此时java程序的入口函数被agent中的premain取代,premain中的内容将在主程序运行前先执行。

        例如,在安装了OpenRASP的tomcat服务器中,catalina.sh文件中会多出Java Agent的启动参数:

  1. ### BEGIN OPENRASP - DO NOT MODIFY ###
  2. if [ "$RASP_DISABLE"x != "Y"x ]; then JAVA_OPTS="-javaagent:${CATALINA_HOME}/rasp/rasp.jar ${JAVA_OPTS}"; fi
  3. JDK_JAVA_OPTIONS="$JDK_JAVA_OPTIONS --add-opens=java.base/jdk.internal.loader=ALL-UNNAMED"
  4. export JDK_JAVA_OPTIONS
  5. ### END OPENRASP ###

2. 动态agent

        与静态方式不同,动态agent允许代理的目标程序的JVM先启动,再通过attach机制载入。

        和premain模式不同,不再通过添加启动参数的方式来连接agent和主程序了。attach方式使用了com.sun.tools.attach包下的VirtualMachine工具类。需要注意的是这个包不是jvm标准规范,需要引入依赖。以attach方式启动需要在agent中实现如下方法:

  1. public static void agentmain(String agentArgs, Instrumentation inst);
  2. public static void agentmain(String agentArgs);

        使用动态agent的方式如下:

        1)编写agentmain函数 ,如上面所述。例如OpenRASP也包含了动态agent的入口函数:

  1. /**
  2. * attach 机制加载 agent
  3. *
  4. * @param agentArg 启动参数
  5. * @param inst {@link Instrumentation}
  6. */
  7. public static void agentmain(String agentArg, Instrumentation inst) {
  8. init(Module.START_MODE_ATTACH, agentArg, inst);
  9. }

        2)定义一个MANIFEST.MF文件,与静态agent不同的是,此时必须包含Agent-Class选项。

        3)将包含agentmain的类和MANIFEST.MF文件打成一个jar包。

        4)在主程序开始执行后,通过attach工具加载Agent。Agent启动后会监听VMInit事件,在 JVM 初始化完成之后创建 InstrumentationImpl 对象,监听 ClassFileLoadHook 事件,然后调用 InstrumentationImpl 的loadClassAndCallAgentmain方法,该方法中会调用Agent里MANIFEST.MF所指定的Agent-Class类的agentmain方法。

        在编写agentmain方法时会用以下方法来实现attach机制:

  1. List<VirtualMachineDescriptor> list = VirtualMachine.list();
  2. // 列出所有VM实例
  3. VirtualMachine.attach(descriptor.id());
  4. // attach目标VM
  5. VirtualMachine#loadAgent("代理Jar路径","命令参数");
  6. // 目标VM加载Agent

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/349095
推荐阅读
相关标签
  

闽ICP备14008679号