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 Api:JVM允许我们动态传递.java文件,使用JVM编译java生成class。
BTrace的jar包:
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
分享到:
相关推荐
BTrace通过字节码注入技术实现其功能。它会在类加载时动态修改类的字节码,插入追踪代码,而无需修改原有源代码。这使得BTrace成为一种非常灵活的诊断工具,尤其适合生产环境,因为它不会影响程序的正常执行流程。 ...
《深入解析BTrace:源代码探索之旅》 BTrace,全称为"Berkeley Trace",是一款强大的、动态的、安全的Java生产环境诊断工具。它允许开发者在运行时对Java应用程序进行性能分析和故障排查,而无需修改或重启应用。...
BTrace 提供了一系列的内建脚本函数和注解,如 @OnMethod、@Trace、@BTrace、@Probe 等,方便开发者编写探针脚本。这些脚本通常用 Java 编写,可以访问诸如系统属性、线程信息、堆栈跟踪等各种运行时数据。 5. **...
文章提供了一些关于JVM内部工作原理的深入洞察,但需要指出,由于文章的快速发布的性质,内容主要是基于作者个人经验的趣闻轶事,可能没有经过正式的技术审查,因此使用和信任这些内容需要谨慎。 在给出的内容中,...
轴类零件加工工艺设计.zip
资源内项目源码是来自个人的毕业设计,代码都测试ok,包含源码、数据集、可视化页面和部署说明,可产生核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果、标签分布图。都是运行成功后才上传资源,毕设答辩评审绝对信服的保底85分以上,放心下载使用,拿来就能用。包含源码、数据集、可视化页面和部署说明一站式服务,拿来就能用的绝对好资源!!! 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、大作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.txt文件,仅供学习参考, 切勿用于商业用途。
seaborn基本绘图人力资源数据集
移动机器人(sw三维)
自制html网页源代码查看器
3吨叉车的液压系统设计().zip
1_实验三 扰码、卷积编码及交织.ppt
北京交通大学软件学院自命题科目考试大纲.pdf
雅鲁藏布江流域 shp矢量数据 (范围+DEM).zip
基于RUST的数据结构代码示例,栈、队列、图等
NIFD:2024Q1房地产金融报告
详细介绍及样例数据:https://blog.csdn.net/li514006030/article/details/146916652
【工业机器视觉定位软件Vision-Detect】基于C#的WPF与Halcon开发的工业机器视觉定位软件(整套源码),开箱即用 有用户登录,图片加载,模板创建,通讯工具,抓边抓圆,良率统计,LOG日志,异常管理,九点标定和流程加载保存等模块,功能不是很完善,适合初学者参考学习。 资源介绍请查阅:https://blog.csdn.net/m0_37302966/article/details/146912206 更多视觉框架资源:https://blog.csdn.net/m0_37302966/article/details/146583453
内容概要:本文档详细介绍了Java虚拟机(JVM)的相关知识点,涵盖Java内存模型、垃圾回收机制及算法、垃圾收集器、内存分配策略、虚拟机类加载机制和JVM调优等内容。首先阐述了Java代码的编译和运行过程,以及JVM的基本组成部分及其运行流程。接着深入探讨了JVM的各个运行时数据区,如程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区等的作用和特点。随后,文档详细解析了垃圾回收机制,包括GC的概念、工作原理、优点和缺点,并介绍了几种常见的垃圾回收算法。此外,文档还讲解了JVM的分代收集策略,新生代和老年代的区别,以及不同垃圾收集器的工作方式。最后,文档介绍了类加载机制、JVM调优的方法和工具,以及常用的JVM调优参数。 适合人群:具备一定Java编程基础的研发人员,尤其是希望深入了解JVM内部机制、优化程序性能的技术人员。 使用场景及目标:①帮助开发人员理解Java代码的编译和执行过程;②掌握JVM内存管理机制,包括内存分配、垃圾回收等;③熟悉类加载机制,了解类加载器的工作原理;④学会使用JVM调优工具,掌握常用调优参数,提升应用程序性能。 其他说明:本文档内容详尽,适合用作面试准备材料和技术学习资料,有助于提高开发人员对JVM的理解和应用能力。
Android项目原生java语言课程设计,包含LW+ppt
戴德梁行&中国房地产协会:2021亚洲房地产投资信托基金研究报告