美文网首页
Java黑魔法之Java Agent[译]

Java黑魔法之Java Agent[译]

作者: 库洛琪 | 来源:发表于2019-03-26 09:29 被阅读0次
1. 介绍

在这最后一篇教程中我们将来介绍Java agent,这是普通Java开发者的黑魔法。Java agent能通过直接修改字节码侵入正运行于JVM上的Java应用。它危险而强大:它几乎能够做所有事情,但一旦出错,可以轻易地导致JVM崩溃。

这部分的目标是通过解释它如何工作,如何运行它并通过一些能展示它优势的简单的例子来揭开Java agent的什么面纱。

2. Java Agent基础

在本质上,Java agent是一个遵循一组严格约定的普通类。agent类必须实现public static void premain(String agentArgs, Instrumentation inst) 方法,从而成为一个代理入口(就像普通类的main方法)。

一旦JVM初始化,每一个agent的premain(String agentArgs, Instrumentation inst)方法就会在JVM启动时按照指定的顺序被调用。当初始化步骤完成了,真正的Java应用的main方法就会被调用。

然而,如果类没有实现public static void premain(String agentArgs, Instrumentation inst) 方法,JVM会寻找并调用重载版本public static void premain(String agentArgs)。需要注意的是每一个premain方法必须要返回,以便JVM的启动过程能够继续下去。

最后但很重要的是,Java agent也可能有public static void agentmain(String agentArgs, Instrumentation inst)或者public static void agentmain(String agentArgs)方法将会在JVM启动之后执行。

初看起来很简单,但是Java agent实现的时候必须要提供manifest文件。通常位于META-INF文件夹下名为MANIFEST.MF,包含了与包分发相关的各种元数据。

下表是为打包成JAR文件的Java agent定义的一些属性:

Manifest Attribute Description
Premain-Class When an agent is specified at JVM launch time this attribute defines the Java agent class: the class containing the premain method. When an agent is specified at JVM launch time this attribute is required. If the attribute is not present the JVM will abort.
Agent-Class If an implementation supports a mechanism to start Java agents sometime after the JVM has started then this attribute specifies the agent class: the class containing the agentmain method. This attribute is required and the agent will not be started if this attribute is not present.
Boot-Class-Path A list of paths to be searched by the bootstrap class loader. Paths represent directories or libraries.
Can-Redefine-Classes A value of true or false, case-insensitive and defines if the ability to redefine classes needed by this agent. This attribute is optional, the default is false.
Can-Retransform-Classes A value of true or false, case-insensitive and defines if the ability to retransform classes needed by this agent. This attribute is optional, the default is false.
Can-Set-Native-Method-Prefix A value of true or false, case-insensitive and defines if the ability to set native method prefix needed by this agent. This attribute is optional, the default is false.
3. Java agent与Instrumentation

Java agent的检测功能是无限的,值得关注的但不仅仅限于这些:

  • 运行时重新定义类的能力。重定义将可能改变方法体、常量池和属性。重定义不能增加、删除或者重命名域和方法,不能修改方法的签名、不能改变继承关系。
  • 运行时重新转换类的能力。转换将可能改变方法体、常量池和属性。转换不能增加、删除或者重命名域和方法,不能修改方法的签名、不能改变继承关系。

需要注意的是转换和重定义类的字节码是没有验证的,当转换和重定义执行完成后类直接被虚拟机装入了,如果这些字节码是错误的,将会抛出异常并导致JVM崩溃。

4. 编写你的第一个Java agent

在这一节中将通过实现了自己的类转换器的Java agent。话虽如此,使用Java代理的唯一缺点是,为了完成或多或少有用的转换,需要直接的字节码操作技能。并且,不幸的是,标准的Java库并没有提供能进行这些字节码操作的API。

为了填补这个空白,非常有创造力的Java社区,提出了一些优秀的,现在已经非常成熟的库,例如Javassist和ASM。这两者之间,Javassist更易于使用,这也是我们将使用它作为字节码操作解决方案的原因。

下面的例子非常简单,但却是来自于真实案例。我们将获取每一个由Java应用打开的HTTP连接的URL。通过直接修改源代码会有许多方式来实现它,但假设因为许可证或者其他的,我们没法获取到源代码。

一个创建HTTP连接的典型例子如下所示:

public class SampleClass {
    public static void main( String[] args ) throws IOException {
        fetch("http://www.baidu.com");
        fetch("http://www.tencent.com");
    }
 
    private static void fetch(final String address) 
            throws MalformedURLException, IOException {
 
        final URL url = new URL(address);                
        final URLConnection connection = url.openConnection();
         
        try( final BufferedReader in = new BufferedReader(
                new InputStreamReader( connection.getInputStream() ) ) ) {
             
            String inputLine = null;
            final StringBuffer sb = new StringBuffer();
            while ( ( inputLine = in.readLine() ) != null) {
                sb.append(inputLine);
            }       
             
            System.out.println("Content size: " + sb.length());
        }
    }
}

Java agent非常适合解决这个问题。仅仅需要定义一个transformer,通过注入代码到sun.net.www.protocol.http.HttpURLConnection的构造器中来输出URL到控制台。听起来很恐怖,但有了ClassFileTransformer和Javassist,这非常简单。下面是一个transformer的实现:

public class SimpleClassTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform( 
            final ClassLoader loader, 
            final String className,
            final Class<?> classBeingRedefined, 
            final ProtectionDomain protectionDomain,
            final byte[] classfileBuffer ) throws IllegalClassFormatException {
        
        if (className.endsWith("sun/net/www/protocol/http/HttpURLConnection")) {
            try {
                final ClassPool classPool = ClassPool.getDefault();
                final CtClass clazz = classPool.get("sun.net.www.protocol.http.HttpURLConnection");
                
                for (final CtConstructor constructor: clazz.getConstructors()) {
                    constructor.insertAfter("System.out.println(this.getURL());");
                }
    
                byte[] byteCode = clazz.toBytecode();
                clazz.detach();
                
                return byteCode;
            } catch (final NotFoundException | CannotCompileException | IOException ex) {
                ex.printStackTrace();
            }
        }
        
        return null;
    }
}

ClassPool类和所有的CtXxx类(CtClassCtConstructor)来自于Javassist包。这里的transformer非常原始,但作为示例已经足够了。首先,因为我们只对HTTP感兴趣,sun.net.www.protocol.http.HttpURLConnection该类负责HTTP连接。

首先需要注意className使用的是“/”分隔符而不是“.”。然后找到HttpURLConnection类通过注入System.out.println(this.getURL());来修改了它所有的构造器。最后返回被修改类的字节码,这将被JVM用以替换原来的类。

这样,Java agent的premain方法的作用就是将SimpleClassTransformer的实例添加到Instrumentation的上下文中

public class SimpleAgent {
    public static void premain(String agentArgs, Instrumentation inst) {
        final SimpleClassTransformer transformer = new SimpleClassTransformer();
        inst.addTransformer(transformer);
    }
}

为了完成Java agent,需要提供适当的MANIFEST.MF,以便JVM能获取到正确的类。下面是所需属性的最小集合。

Manifest-Version: 1.0
Premain-class: com.javacodegeeks.advanced.agent.SimpleAgent

(使用maven-jar-plugin可以直接配置manifest的相关属性。)

5. 运行Java Agent

从命令行执行时,通过使用参数-javaagent将Java agent传递给JVM实例,语法如下:

-javaagent:<path-to-jar>[=options]

<path-to-jar>是Java agent JAR文件的路径,options是一些其他传递给Java agent的参数,即是agentArgs参数。举个例子来说,在我们的Java agent中,可以使用以下方式:

java -javaagent:advanced-java-part-15-java7.agents-0.0.1-SNAPSHOT.jar

使用Java agent advanced-java-part-15-java7.agents-0.0.1-SNAPSHOT.jar来运行SampleClass,可以在控制台打印尝试使用HTTP协议的URL。

http://www.baidu.com
Content size: 2309
http://www.tencent.com
Content size: 180

不使用Java agent来运行SimpleClass将只会在控制台打印内容大小,不会有URL。

Content size: 2309
Content size: 180

JVM使得运行Java agent十分容易。但是,需要注意,任何错误的字节码会导致JVM崩溃,可能会丢失您的应用程序此时拥有的重要数据。

6. 原文地址

Java Agents

相关文章

  • Java黑魔法之Java Agent[译]

    1. 介绍 在这最后一篇教程中我们将来介绍Java agent,这是普通Java开发者的黑魔法。Java agen...

  • RASP研发踩坑之 热加载与热卸载

    在RASP研发踩坑之agent 加载机制中,当想要卸载 Java agent 时,原生Java Agent 不能够...

  • IDEA + maven 零基础构建 java agent 项目

    200316-IDEA + maven 零基础构建 java agent 项目 Java Agent(java 探...

  • 破解 Java Agent 探针黑科技!

    一、什么是 Java Agent ? 笼统地来讲,Java Agent 是一个统称,该功能是 Java 虚拟机提供...

  • Btrace

    在之前介绍Java Agent的这篇文章中,简单介绍了Java agent的应用,里面提到了基于Java agen...

  • JAVA进阶之Agent

    1、什么是 Java Agent 笼统地来讲,Java Agent 是一个统称,该功能是 Java 虚拟机提供的一...

  • Java探针(javaagent)

    JDK1.5开始引入了Agent机制(即启动java程序时添加“-javaagent”参数,Java Agent机...

  • Java agent初识

    Java agent又叫做Java 探针,本文将从以下四个问题出发来深入浅出了解下Java agent 一、什么是...

  • Java Agent (JVM Instrumentation

    Java Agent 简介 Java 代理 (agent) 是在你的main方法前的一个拦截器 (intercep...

  • java agent开发

    java agent介绍 java agent是jvm插件或者叫做代理,她是运行在main方法之前,她内定的方法名...

网友评论

      本文标题:Java黑魔法之Java Agent[译]

      本文链接:https://www.haomeiwen.com/subject/semwvqtx.html