`
mgoann
  • 浏览: 253145 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

BTrace系列之五:实现原理

阅读更多

BTrace系列之五:实现原理

原理简介

BTrace的实现原理其实早有博文阐述,详细见这里。

我在这里只所以在此进行阐述是想带着大家思考一下这些原理的背后,我们是否也能够运用这些原理去实现一些意想不到的事情,其实就是一个抛砖引玉的过程,希望大家能够多多进行讨论,搞出一些开源的好的工具也没准。

 

BTrace整个实现的原理是Java Agent+ASM+Java instrument+ Java Complier Api

首先来阐述一下各个原理:

 

Java Agent技术:

代理被部署为 JAR 文件。JAR 文件清单中的属性指定将被加载以启动代理的代理类。对于支持命令行接口的实现,可以在命令行指定一个选项来启动代理。实现也支持在 VM 启动后某一时刻启动代理的机制。例如,实现可以提供一种机制,允许某种工具连接 到运行的应用程序,并将该工具代理的加载初启到该应用程序中。如何初启加载的细节与实现有关。

对于使用命令行接口的实现,可以将以下选项添加到命令行来启动代理:

 

-javaagent:jarpath[=options]

jarpath 是代理 JAR 文件的路径。 options 是代理选项。此开关可以在同一代码行使用多次,从而创建多个代理。多个代理可以使用相同的 jarpath。代理 JAR 文件必须遵守 JAR 文件规范。

代理 JAR 文件的清单必须包含 Premain-Class 属性。此属性的值是代理类 的名称。代理类必须实现公共静态 premain 方法,该方法的原理与 main 应用程序入口点类似。在 Java 虚拟机 (JVM) 初始化后,每个 premain 方法将按照指定代理的顺序调用,然后将调用实际的应用程序 main 方法。每个 premain 方法必须按照依次进行的启动顺序返回。

 

premain 方法有两种可能的签名。JVM 首先尝试在代理类上调用以下方法:

 

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

如果代理类没有实现此方法,那么 JVM 将尝试调用:

 

public static void premain(String agentArgs);

如果代理在 VM 启动之后启动,那么代理类还有一个要使用的 agentmain 方法。如果是使用命令行选项启动代理,那么 agentmain 方法将不会被调用。

 

代理类将被系统类加载器加载(参见 ClassLoader.getSystemClassLoader)。系统类加载器是通常加载包含应用程序 main 方法的类的类加载器。 premain 方法将在与应用程序 main 方法相同的安全性和类加载器规则下运行。不存在代理 premain 方法可以执行的建模限制。任何应用程序 main 可以执行的事情(包括创建线程)对于 premain 都是合法的。

 

每个代理通过 agentArgs 参数传递其代理选项。代理选项作为单个字符串传递,任何其他解析应由代理本身执行。

 

如果该代理不能被解析(例如,由于无法加载代理类,或者代理类没有恰当的 premain 方法),那么 JVM 将中止。如果 premain 方法抛出未捕获的异常,那么 JVM 将中止。

Premain-Class

如果 JVM 启动时指定了代理,那么此属性指定代理类,即包含 premain 方法的类。如果 JVM 启动时指定了代理,那么此属性是必需的。如果该属性不存在,那么 JVM 将中止。注:此属性是类名,不是文件名或路径。

Agent-Class

如果实现支持 VM 启动之后某一时刻启动代理的机制,那么此属性指定代理类。 即包含 agentmain 方法的类。 此属性是必需的,如果不存在,代理将无法启动。 注:这是类名,而不是文件名或路径。

Boot-Class-Path

由引导类加载器搜索的路径列表。路径表示目录或库(在许多平台上通常作为 JAR zip 库被引用)。查找类的特定于平台的机制失败后,引导类加载器会搜索这些路径。按列出的顺序搜索路径。列表中的路径由一个或多个空格分开。路径使用分层 URI 的路径组件语法。如果该路径以斜杠字符(“/”)开头,则为绝对路径,否则为相对路径。相对路径根据代理 JAR 文件的绝对路径解析。忽略格式不正确的路径和不存在的路径。如果代理是在 VM 启动之后某一时刻启动的,则忽略不表示 JAR 文件的路径。此属性是可选的。

Can-Redefine-Classes

布尔值(true false,与大小写无关)。是否能重定义此代理所需的类。true 以外的值均被视为 false。此属性是可选的,默认值为 false

Can-Retransform-Classes

布尔值(true false,与大小写无关)。是否能重转换此代理所需的类。true 以外的值均被视为 false。此属性是可选的,默认值为 false

Can-Set-Native-Method-Prefix

布尔值(true false,与大小写无关)。是否能设置此代理所需的本机方法前缀。true 以外的值均被视为 false。此属性是可选的,默认值为 false

Java instrument

实现可以提供一种机制在 VM 启动之后某一时刻启动代理。如何初启的细节是特定于实现的,但通常应用程序已经启动并且其 main 方法已经调用。如果实现支持在 VM 启动之后启动代理,则以下内容适用:

 

代理 JAR 的清单必须包含属性 Agent-Class。此属性的值是代理类 的名称。代理类必须实现公共静态 agentmain 方法。系统类加载器(ClassLoader.getSystemClassLoader)必须支持将代理 JAR 文件添加到系统类路径的机制。代理 JAR 将被添加到系统类路径。系统类路径是通常加载包含应用程序 main 方法的类的类路径。代理类将被加载,JVM 尝试调用 agentmain 方法。JVM 首先尝试对代理类调用以下方法:

 

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

如果代理类没有实现此方法,那么 JVM 将尝试调用:

 

public static void agentmain(String agentArgs);

如果代理是使用命令行选项启动的,那么代理类还有一个要使用的 agentmain 方法。如果是在 VM 启动之后启动代理,那么 agentmain 方法将不会被调用。

 

代理通过 agentArgs 参数传递其代理选项。代理选项作为单个字符串传递,任何其它解析应该由代理本身执行。

 

agentmain 方法应该执行任何启动代理所需的初始化。启动完成时该方法应返回。如果代理无法启动(例如,由于无法加载代理类,或者代理类没有构造 agentmain 方法),那么 JVM 将中止。如果 agentmain 方法抛出未捕获的异常,那么它将被忽略。

脚本解析引擎:

Java Complier ApiJVM允许我们动态传递.java文件,使用JVM编译java生成class

 

 

代码实现

BTracejar包:

btrace-agent.jar

btrace-boot.jar

btrace-client.jar

三个jar对应了BTrace的工作原理中角色。



 

整个代码的入口处是:

Com.sun.btrace.client.Main,我们使用btrace <pid> xxx.java就是运行的这个类的main方法。

Main方法中主要做了如下四件事情:

1.创建一个客户端;

2.编译trace脚本;

3.动态attach一个agent

4.创建客户端Socket与服务端Socket通信。

 

Client client = new Client(port, PROBE_DESC_PATH, DEBUG,
                    TRACK_RETRANSFORM, UNSAFE, DUMP_CLASSES, DUMP_DIR);
            if (!new File(fileName).exists())
            {
                errorExit("File not found: " + fileName, 1);
            }

            // 进行编译,注意现在还没有使用ASM进行字节码修改,只是经过语法校验器的检查
            byte[] code = client.compile(fileName, classPath, includePath);
            if (code == null)
            {
                errorExit("BTrace compilation failed", 1);
            }

            // 动态的启动agent
            client.attach(pid);
            registerExitHook(client);
            if (con != null)
            {
                registerSignalHandler(client);
            }
            if (isDebug())
                debugPrint("submitting the BTrace program");

            // 启动客户端Socket进行链接,将编译后的字节码和参数传输到服务端
            client.submit(fileName, code, btraceArgs,
                    createCommandListener(client));

 

 

 

我们具体看下如何进行编译的

 

 

this.unsafe = unsafe;
        //获取JVMTI的编译器
        this.compiler = ToolProvider.getSystemJavaCompiler();
        this.stdManager = compiler.getStandardFileManager(null, null, null);

 

 

其中com.sun.btrace.compiler.Compiler提供了Main方法,也可以单独进行编译BTrace脚本。

 

具体用法如下:

 

 

Usage: btracec <options> <btrace source files>
where possible options include:
  -classpath <path> Specify where to find user class files and annotation processors
  -cp <path>        Specify where to find user class files and annotation processors
  -I <path>         Specify where to find include files
  -d <directory>    Specify where to place generated class files

 

// 使用verifier进行是BTrace脚本的语法校验器,进行例如no loop等校验
        Verifier btraceVerifier = new Verifier(unsafe);

 

 

BTrace的安全性检查是通过Verifier来实现的,规范可参照JSR 269。只要将该类重写就可以突破BTrace的安全限制,不过请慎重,这样可能会影响到目标进程。

ClassReader cr = new ClassReader(classBytes.get(name));
                    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
                    cr.accept(new Postprocessor(cw), ClassReader.EXPAND_FRAMES
                            + ClassReader.SKIP_DEBUG);

  

在经过编译之后,使用ASM进行了一次字节码的修改,我们看下这个在Client端的字节码修改是在做什么操作?

 

其实客户端并没有对编译好的脚本的字节码进行修改,只是针对其做了一些兼容性,例如,域访问控制器、简写。

 

到目前为止整个编译过程就进行完了。

 

接下来该介绍Agent技术了。

VirtualMachine vm = null;
            
            vm = VirtualMachine.attach(pid);
           
            vm.loadAgent(agentPath, agentArgs);

  

这里可以看到就是通过这几行代码,从BTrace的客户端,触发了目标JVM上的agent启动,这样就正式从。

 

BTrace Agent分析:

 

public static void premain(String args, Instrumentation inst) {
        main(args, inst);
    }

    public static void agentmain(String args, Instrumentation inst) {
        main(args, inst);
}

  

 

Main:方法中所做的事情就是:

1.添加boot classpath

inst.appendToBootstrapClassLoaderSearch(new JarFile(new File(path)));
              
inst.appendToSystemClassLoaderSearch(new JarFile(new File(path)));

  

2.启动服务端Socket

ServerSocket ss;
        try {
            if (isDebug()) debugPrint("starting server at " + port);
            System.setProperty("btrace.port", String.valueOf(port));
            if (scriptOutputFile != null && scriptOutputFile.length() > 0) {
                System.setProperty("btrace.output", scriptOutputFile);
            }
            ss = new ServerSocket(port);
        } catch (IOException ioexp) {
            ioexp.printStackTrace();
            return;
        }

  

3. 注册提供的class转换器。

 

inst.addTransformer(clInitTransformer, false);
        inst.addTransformer(this, true);

 

 

4.ASM进行字节码修改,并进行类转换。

 

Client implements ClassFileTransformer
public byte[] transform(
                ClassLoader loader,
                String cname,
                Class<?> classBeingRedefined,
                ProtectionDomain protectionDomain,
                byte[] classfileBuffer)
        throws IllegalClassFormatException {


private byte[] instrument(Class clazz, String cname, byte[] target) {
        byte[] instrumentedCode;
        try {
            ClassWriter writer = InstrumentUtils.newClassWriter(target);
            ClassReader reader = new ClassReader(target);
            Instrumentor i = new Instrumentor(clazz, className,  btraceCode, onMethods, writer);
            InstrumentUtils.accept(reader, i);
            if (Main.isDebug() && !i.hasMatch()) {
                Main.debugPrint("*WARNING* No method was matched for class " + cname); // NOI18N
            }
            instrumentedCode = writer.toByteArray();
        } catch (Throwable th) {
            Main.debugPrint(th);
            return null;
        }
        Main.dumpClass(className, cname, instrumentedCode);
        return instrumentedCode;
    }

  

 


 

  • 大小: 13.2 KB
  • 大小: 13.3 KB
3
0
分享到:
评论
1 楼 lixia0417 2016-02-25  
如果代理是使用命令行选项启动的,那么代理类还有一个要使用的 agentmain 方法。如果是在 VM 启动之后启动代理,那么 agentmain 方法将不会被调用。楼主,这里有错啊。应该是premain方法才对。

如果代理无法启动(例如,由于无法加载代理类,或者代理类没有构造 agentmain 方法),那么 JVM 将中止。如果 agentmain 方法抛出未捕获的异常,那么它将被忽略。
这里也有错吧。没有agentmain方法的时候也是忽略

相关推荐

    BTrace实现原理

    【BTrace实现原理详解】 BTrace,一个强大的Java诊断工具,其主要功能是在线无侵入地对生产环境中的Java应用程序进行动态跟踪和性能分析。它的实现原理主要依赖于四个核心组件:Java Agent、ASM、Java Instrument ...

    BTrace二三事之二:OnMethod子类匹配BUG(怀疑)

    标签 "源码" 和 "工具" 提示我们,问题可能涉及到BTrace的源代码实现,也可能需要通过理解工具的工作原理来解决问题。对于源码层面的问题,可能需要查看BTrace的开源代码库,找出潜在的bug或者逻辑错误。对于工具...

    btrace支持jdk1.7 linux下使用

    BTrace通过字节码注入技术实现其功能。它会在类加载时动态修改类的字节码,插入追踪代码,而无需修改原有源代码。这使得BTrace成为一种非常灵活的诊断工具,尤其适合生产环境,因为它不会影响程序的正常执行流程。 ...

    btrace源代码

    《深入解析BTrace:源代码探索之旅》 BTrace,全称为"Berkeley Trace",是一款强大的、动态的、安全的Java生产环境诊断工具。它允许开发者在运行时对Java应用程序进行性能分析和故障排查,而无需修改或重启应用。...

    btrace引导文件安装Btrace

    Btrace作为一个强大的Java应用监控工具,通过动态字节码注入技术,让我们无需修改代码就能实现运行时监控。结合VisualVM插件,我们可以更直观地管理和分析监控数据,这对于调试、性能优化以及问题排查具有极大的价值...

    btrace-demo:demo项目

    2. **工作原理**:BTrace通过Java Agent机制,在JVM启动时动态地将BTrace脚本插入到目标应用中,然后在特定的触发条件(如方法调用、特定类加载等)下执行这些脚本。 3. **BTrace脚本**:BTrace使用一种类似于Java...

    Btrace 学习1

    BTrace 提供了一系列的内建脚本函数和注解,如 @OnMethod、@Trace、@BTrace、@Probe 等,方便开发者编写探针脚本。这些脚本通常用 Java 编写,可以访问诸如系统属性、线程信息、堆栈跟踪等各种运行时数据。 5. **...

    btrace1.3.9.zip

    它的核心原理是通过ASM库来生成字节码,实现在目标类的方法中插入监控代码,以收集运行时数据。 二、BTrace 1.3.9与JDK 1.8兼容性 BTrace 1.3.9版本针对JDK 1.8进行了深度适配,确保在Java 8环境下依然能稳定高效...

    Btrace 官方zip包

    它通过使用Java代理技术(Java Instrumentation API)动态插入代码来实现对目标应用程序的跟踪。Btrace 提供了一种灵活的脚本语言,用户可以编写简单的脚本来定义需要监控的行为,如方法调用统计、资源消耗分析等。 ...

    btrace_extend-master

    【标题】"btrace_extend-master" 是一个与BTrace工具相关的扩展项目,它提供了一些额外的功能和增强,以便更好地监控和诊断Java应用程序。BTrace是一款强大的动态代码分析工具,允许开发者在运行时对Java应用程序...

    BTrace的安装包Windows

    **BTrace 安装包Windows详解** BTrace是一款强大的Java应用程序动态跟踪工具,它允许开发者在不修改代码的情况下,对正在运行的应用进行诊断和性能分析。这款工具在Java社区中广受欢迎,因为它提供了灵活的探查...

    btrace1.3.9

    【标题】"btrace1.3.9"指的是BTrace工具的一个特定版本,1.3.9,这是一款强大的Java应用程序动态跟踪工具。BTrace利用Java的JVMTI(Java Virtual Machine Tool Interface)和Serviceability Agent(SA)来提供安全、...

    jvisualvm btrace插件离线安装

    在Java开发领域,性能优化是不可或缺的一环,而BTrace作为一个强大的动态跟踪工具,可以帮助开发者实时监控和分析应用程序的行为。本文将详细介绍如何在jVisualVM上进行BTrace插件的离线安装,以便在无法在线安装的...

    Btrace资源

    **Btrace工作原理** Btrace利用了Java的 JVMTI (Java Virtual Machine Tool Interface) 技术,JVMTI允许外部工具如Btrace与Java虚拟机交互,提供诸如挂起线程、修改类、获取堆信息等功能。Btrace通过分析和修改类...

    btrace调试工具

    总结,BTrace作为一款强大的Java在线调试工具,通过字节码增强技术实现了对运行中的Java应用的实时监控,对于开发人员而言,是进行性能优化和问题排查的得力助手。合理运用BTrace,可以极大地提升线上问题解决的效率...

    btrace release-1.2.5.1

    7. docs:这个目录下通常包含BTrace的用户手册、API文档和其他技术文档,是学习和理解BTrace功能和工作原理的关键资源。 8. bin:这个目录存放可执行文件和脚本,如BTrace的命令行工具,用户可以直接运行这些文件来...

    btrace工具

    Btrace:java性能调优及问题追踪工具 Btrace:java性能调优及问题追踪工具

    btrace-release-1.3.11.3.zip

    标题中的 "btrace-release-1.3.11.3.zip" 表明这是一个 Btrace 的特定发布版本,版本号为 1.3.11.3,通常包含已编译的库文件、文档和其他必要的资源。 在 Java 开发中,性能监控是至关重要的,因为这有助于识别和...

Global site tag (gtag.js) - Google Analytics