Java Agent 动态修改字节码详情

目录
  • 1、什么是Java Agent
  • 2、实现Java Agent
    • 2.1 类转换
    • 2.2 使用Java代理进行实际操作

假设您有一个在生产环境中运行的应用程序。每隔一段时间,它就会进入中断状态,错误很难重现,您需要从应用程序中获得更多信息。

那么你想知道解决方案吗?

您可以做的是动态地将一些代码集附加到应用程序中,并仔细地重写它,以便代码转储您可以记录的其他信息,或者您可以将应用程序阶段转储到文本文件中。Java为我们提供了使用Java Agent实现这一点的工具。

你有没有想过我们的Java代码是如何在IDE中进行热交换的?这是因为特工。关于Java Agent的另一个有趣的事实是,应用程序探查器在后端使用相同的技术来收集内存使用情况、内存泄漏和方法执行时间的信息。

1、什么是Java Agent

Java Agent是一种特殊类型的类,通过使用Java Instrumentation API,它可以拦截JVM上运行的应用程序,修改它们的字节码。Java Agent非常强大,也非常危险。

在开始之前,我将解释Java Agent如何使用简单的HelloWorld示例拦截类。

public class Hello {
    public static void main(String[] args){
        System.out.println("hello world");
    }
}

如下图所示:

类加载器负责将类从二进制加载到内存中。运行编译后的HelloWorld应用程序(HelloWorld.class)时,可以将Agent视为在运行时拦截类加载器行为的一种方式。您可能会想,java字节代码是如何被重新构造的,以便Agent可以在正确的位置添加相关代码的。有趣的是,对于Java程序来说,字节码的结构非常接近原始Java程序源代码。因此,虽然我们不为Java程序本身添加工具,但我们使用了它的一个非常接近的表示形式。需要注意的是,有一些非Java语言可以编译成Java字节码(如Scala、Clojure和Kotlin),这意味着程序字节码的结构和形状可能会非常不同。

2、实现Java Agent

JavaAgent基于来自Java平台的facility,它的入口点是Java.lang instrument包,它提供了允许Agent为JVM上运行的程序提供工具的服务。该包非常简单且自包含,因为它包含两个异常类、一个数据类、类定义和两个接口。在这两种方法中,如果我们想编写Java Agent,我们只需要实现classFileTransformer接口。

定义代理有两种方法。

第一个是静态代理,这意味着我们构建Agent代理,将其打包为jar文件,当我们启动Java应用程序时,我们传入一个名为java agent的特殊JVM参数。然后我们给它代理jar在磁盘上的位置,然后JVM发挥它的魔力。

$ java -javaagent:<path of agent jar file> -jar <path of the packaged jar file you want to intecept>

我们需要添加一个特殊的清单条目,称为pre-main类,当然,这是一个完全限定的名称类定义。

Premain-Class : org.example.JavaAgent

这个类看起来像这样

public class JavaAgent {
    /**
     * As soon as the JVM initializes, This  method will be called.
     *
     * @param agentArgs       The list of agent arguments
     * @param instrumentation The instrumentation object
     * @throws InstantiationException
     */
    public static void premain(String agentArgs, Instrumentation instrumentation) throws InstantiationException {
        InterceptingClassTransformer interceptingClassTransformer = new InterceptingClassTransformer();
        interceptingClassTransformer.init();
        instrumentation.addTransformer(interceptingClassTransformer);
    }
}

premain方法接受两个参数:

  • agentArgs—字符串参数,用户选择作为参数传递给Java Agent调用的任何参数。
  • instrumentation来自java.lang instrument包,我们可以添加一个新的ClassFileTransformer对象,它包含Agent的实际逻辑。

第二个选项称为动态代理。

我们可以做的不是检测启动应用程序的方式,而是编写一小段代码,接收并连接到现有JVM,并告诉它加载某个代理。

VirtualMachine vm = VirtualMachine.attach(vmPid);
vm.load(agentFilePath);
vm.detach();

此参数agentFilePath与静态代理方法中的参数完全相同。它必须是agent jar的文件名,因此没有输入流也没有字节。这种方法有两个警告。第一个是,这是生活在com.sun空间下的私有API,它通常适用于热点实现。第二个问题是,使用Java9进行排序时,您不能再使用此代码连接到它正在运行的JVM。

2.1 类转换

这是我们需要为agent实现的接口,以便转换类。

public interface ClassFileTransformer {
    byte[] transform(ClassLoader loader,
                     String className,
                     Class<?> classBeingRedefined,
                     ProtectionDomain protectionDomain,
                     byte[] classfileBuffer)
            throws IllegalClassFormatException;
}

这有点多,但我将解释方法签名中的必要参数。第一个重要的是类名。此参数的主要目的是帮助查找并区分要拦截的正确类和其他类。显然,您可能不想截取应用程序中的每个类,最简单的方法是使用条件语句进行检查。

然后是类加载器,它主要用于基本应用程序没有平坦类空间的环境中,您可能不需要查看它,但一旦遇到更复杂或模块化的平台,您就需要查看类加载器。classfileBuffer是检测前类的当前定义。要截取它,您需要使用库读取这个字节数组并截取代码,然后必须再次转换回字节码才能返回。

有几个字节码生成库。您需要进行研究,并根据它是高级API还是低级API、社区大小和许可证自行决定。我放在下面的演示是Javassist,因为我认为它在高级和低级API之间有一个很好的平衡,并且是一个三重许可证,所以几乎任何人都可以使用它。这

就是ClassFileTransformer实现的主体。

@Override
public byte[] transform(ClassLoader loader, ..)
        throws .. {
    byte[] byteCode = classfileBuffer;
    // If you wanted to intercept all the classs then you can remove this conditional check.
    if (className.equals("Example")) {
        try {
            ClassPool classPool = scopedClassPoolFactory.create(loader, rootPool,
                    ScopedClassPoolRepositoryImpl.getInstance());
            CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
            CtMethod[] methods = ctClass.getDeclaredMethods();
            for (CtMethod method : methods) {
                if (method.equals("main")) {
                    method.insertAfter("System.out.println(\"Logging using Agent\");");
                }
            }
byteCode = ctClass.toBytecode();
            ctClass.detach();
        } catch (Throwable ex) {
            log.log(Level.SEVERE, "Error in transforming the class: " + className, ex);
        }
    }
    return byteCode;
}
ctClass.detach();
        } catch (Throwable ex) {
            log.log(Level.SEVERE, "Error in transforming the class: " + className, ex);
        }
    }
    return byteCode;
}

在这里,从类池中,我们可以直接绕过classfileBuffer获取类,因为我想使用main方法。我们循环遍历类定义中的所有方法,得到我们想要的类。我们根本不需要使用字节码。我们可以简单地向它传递一些合法的Java代码,然后Javassist将编译它,生成新的字节码,并给出定义。

有三种方法可以向方法中插入一些Java代码。insertAfter(..)在正文末尾插入字节码。它在正文的末尾插入字节码。insertAt(..)在正文的指定行插入字节码,insertBefore(..)在正文的开头插入字节码。

2.2 使用Java代理进行实际操作

从指定的链接下载示例应用程序和Java Agent。

使用进入路径并执行命令mvn clean install来构建这两个repo

现在,您将获得目标中的jar文件。复制示例应用程序中.jar文件的路径,并复制Java Agent中-dependencies.jar文件的路径。

首先,使用命令$java-jar<path of the packaged jar>仅与示例应用程序一起运行应用程序,并观察输出。Hi I am main。将在控制台中打印。

然后,使用命令$ java -javaagent:<path of agent jar file> -jar <path of the packaged jar
file you want to intercept>
并观察输出。将在控制台中另外打印使用代理的日志记录。这确保java agent被拦截并添加到main方法的主体中。

总之,如果要实现Java Agent,请执行以下操作:

1. 您需要创建两个Java类。一个是premain方法(JavaAgent),另一个是扩展ClassFileTransformer的类(CustomTransformer

2. 在premain方法的主体内,需要添加扩展ClassFileTransformer的类的对象

3. 然后,您需要在CustomTransformer中的重写方法transform中添加逻辑。

4. 在转换方法内转换字节码时,可能需要根据用途使用字节码生成库。

5. 您需要在清单中指定premain类并构建jar。

6. 使用javaagent标记将代理加载到要拦截的应用程序中。

到此这篇关于Java Agent动态修改字节码详情的文章就介绍到这了,更多相关Java Agent动态修改字节码内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java 解析user-agent 信息

    解析http user-agent信息,使用uasparser-0.6.1.jar和jregex-1.2_01.jar两个包 import cz.mallat.uasparser.OnlineUpdater; import cz.mallat.uasparser.UASparser; import cz.mallat.uasparser.UserAgentInfo; import java.io.IOException; /** * Created by Edward on 2016/7/1.

  • java agent使用全解析

    今天打算写一下 Java agent,一开始我对它的概念也比较陌生,后来在别人口中听到 字节码插桩,bTrace,Arthas后面才逐渐了解到Java还提供了这么个工具. JVM启动前静态Instrument Java agent 是什么? Java agent是java命令的一个参数.参数 javaagent 可以用于指定一个 jar 包,并且对该 java 包有2个要求: 这个 jar 包的 MANIFEST.MF 文件必须指定 Premain-Class 项. Premain-Class

  • Java Agent入门学习之动态修改代码

    前言 最近用了一下午总算把Java agent给跑通了,本篇文章记录一下具体的操作步骤,以免遗忘.下面话不多说,来一起看看详细的介绍: 通过java agent可以动态修改代码(替换.修改类的定义),进行AOP. 目标: 为所有添加@ToString注解的类实现默认的toString方法 需要两个程序,一个是用来测试的程序,一个agent用于修改代码. 1. 测试程序 被测试的程序包括: - ToString.Java - Foo.java - Main.java 具体代码如下: ToStrin

  • Java Agent 动态修改字节码详情

    目录 1.什么是Java Agent 2.实现Java Agent 2.1 类转换 2.2 使用Java代理进行实际操作 假设您有一个在生产环境中运行的应用程序.每隔一段时间,它就会进入中断状态,错误很难重现,您需要从应用程序中获得更多信息. 那么你想知道解决方案吗? 您可以做的是动态地将一些代码集附加到应用程序中,并仔细地重写它,以便代码转储您可以记录的其他信息,或者您可以将应用程序阶段转储到文本文件中.Java为我们提供了使用Java Agent实现这一点的工具. 你有没有想过我们的Java

  • java虚拟机原理:Class字节码二进制文件分析

    目录 一.字节码文件 与 JVM 二.字节码文件示例 三.字节码文件二进制结构分析 1.魔数 2.次版本号 3.主版本号 4.常量池个数 总结 一.字节码文件 与 JVM Java 源码编译成 Class 字节码 ; Java 虚拟机 可以被认为是一个 解释器 , 解释编译后的 Class 字节码文件 , 最后在不同的操作系统中运行 ; Android 虚拟机 不是 Java 规范的 虚拟机 , 有一些根据嵌入式设备进行的定制的实现 ; Class 字节码 本质上就是 二进制数据 , 运行时 ,

  • Java 将字符串动态生成字节码的实现方法

    可以生成可执行的class文件 直接上能执行代码: 复制代码 代码如下: public class Test { /**  * @param args  */@SuppressWarnings("static-access")public static void main(String[] args) {  try {   new Test().calculate("234 - ( 1 + 45 * 4 ) / 5");  } catch (Exception e)

  • java获取版本号及字节码编译版本方法示例

    前言 之所以会有这篇文章,是因为公司的开发环境比较老,寻找一些jar包的时候总是会纠结对应的编译版本,感觉很麻烦,所以写了一个工具类用于读取class或jar文件的编译版本,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 示例代码 package com.jinggujin.util; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.

  • Java Agent (代理)探针技术详情

    目录 前言: Java Agent 技术简介 Java Agent 功能介绍 Java Agent 实现原理 Java Agent 案例 前言: Java 中的 Agent 技术可以让我们无侵入性的去进行代理,最常用于程序调试.热部署.性能诊断分析等场景,现如今比较火热的分布式链路追踪项目Skywalking,就是通过探针技术去捕获日志,将数据上报OAP观察分析平台. Java Agent 技术简介 Java Agent 直译为 Java 代理,也常常被称为 Java 探针技术. Java Ag

  • 详解Java动态字节码技术

    对 Debug 的好奇 初学 Java 时,我对 IDEA 的 Debug 非常好奇,不止是它能查看断点的上下文环境,更神奇的是我可以在断点处使用它的 Evaluate 功能直接执行某些命令,进行一些计算或改变当前变量. 刚开始语法不熟经常写错代码,重新打包部署一次代码耗时很长,我就直接面向 Debug 开发.在要编写的方法开始处打一个断点,在 Evaluate 框内一次次地执行方法函数不停地调整代码,没问题后再将代码复制出来放到 IDEA 里,再进行下一个方法的编写,这样就跟写 PHP 类似的

  • Java字节码增强技术知识点详解

    简单介绍下几种java字节码增强技术. ASM ASM是一个Java字节码操控框架,它能被用来动态生成类或者增强既有类的功能.ASM可以直接产生class文件,也可以在类被加载入Java虚拟机之前动态改变类行为.ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类. 主页:https://asm.ow2.io/index.html ASM框架中的核心类有以下几个: ① ClassReader:该类用来解析编译过的class字节码文件. ② ClassWriter:

  • Java JVM字节码指令集总结整理与介绍

    目录 Java是怎么跨平台的 平台无关的基石 JVM字节码指令介绍 字节码与数据类型 加载和存储指令 算术指令 类型转换指令 宽化类型转换 窄化类型转换: 对象创建与访问指令 操作数栈管理指令 控制转移指令 方法调用和返回指令 异常处理指令 同步指令 JVM指令集大全 Java是怎么跨平台的 我们上计算机课的时候老师讲过:"计算机只能识别0和1,所以我们写的程序要经过编译器翻译成0和1组成的二进制格式计算机才能执行".我们编译后产生的.class文件是二进制的字节码,字节码是不能被机器

  • Java通过反射,如何动态修改注解的某个属性值

    Java反射动态修改注解的某个属性值 昨晚看到一条问题,大意是楼主希望可以动态得建立多个Spring 的定时任务. 这个题目我并不是很熟悉,不过根据题目描述和查阅相关Spring 创建定时任务的资料,发现这也许涉及到通过Java代码动态修改注解的属性值. 今天对此尝试了一番, 发现通过反射来动态修改注解的属性值是可以做到的: 众所周知,java/lang/reflect这个包下面都是Java的反射类和工具. Annotation注解,也是位于这个包里的.注解自从Java 5.0版本引入后,就成为

随机推荐