如何编写自己的Java / Scala调试器
通过这篇文章,我们将探讨Java / Scala调试器的编写和工作方式。 诸如Windows的WinDbg或Linux / Unix的gdb之类的本机调试器通过操作系统直接提供给它们的钩子来获取其强大功能,以监视和操纵外部进程的状态。 JVM充当OS之上的抽象层,它提供了自己的独立体系结构来调试字节码。
该框架及其API是完全开放的,有文档的和可扩展的,这意味着您可以轻松编写自己的调试器。 该框架的当前设计由两个主要部分组成-JDWP协议和JVMTI API层。 每种方法都有其自己的一套优点和最佳用例。
JDWP协议
Java调试器有线协议通常用于在网络上使用二进制消息在调试器和被调试进程之间传递请求和接收事件(例如线程状态或异常的更改)。 该体系结构背后的概念是在两者之间建立尽可能多的隔离。 这是为了减少让调试器在目标代码运行时改变其执行的海森堡效应(物理学家维尔纳,而不是友好的Meth Cooking Walt )。
从目标进程中删除尽可能多的调试器逻辑也有助于确保已调试VM状态的更改(例如“停止世界” GC或OutOfMemoryErrors)不会影响调试器本身。 为了简化操作,JDK附带了JDI (Java调试器接口),该接口提供了协议的完整调试器端实现,并具有连接,分离,监视和操纵目标VM的状态的能力。
例如,该协议与Eclipse的调试器使用的协议相同。 如果查看在IDE调试时传递给Java进程的命令行参数,您会注意到Eclipse传递给它的其他参数(-agentlib:jdwp = transport = dt_socket,…)来启用JVM调试,并且还会建立发送请求和事件的端口。
JVMTI API
现代JVM调试器体系结构中的第二个关键组件是一组本机API,涵盖了与JVM操作相关的广泛领域,称为JVM工具接口 (即JVMTI)。 与JDWP不同,JVMTI被设计为一组C / C ++ API,并且具有JVM动态加载利用API提供的命令的预编译库(例如.dll或.so)的机制。
这种方法与JDWP的不同之处在于,它实际上在目标进程内部执行调试器。 这增加了调试器在性能和稳定性方面影响应用程序代码的可能性。 但是,关键优势在于能够以近乎实时的方式直接与JVM交互。
由于JVMTI提供了一组功能强大的低级API集,所以我认为有必要进一步深入研究并解释其工作原理,以及可以使用它进行哪些出色的工作。 可通过JDK随附的jvmti.h获得API标头。
编写调试器库
编写自己的调试器需要使用C ++创建本机OS库。 在这种情况下,您的“主要”功能看起来像–
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void*)
当您的调试器代理由JVM加载时,该函数将由JVM调用。 传递给您的日益重要的JavaVM指针将为您提供与JVM对话所需的一切。 它引入了JavaVM :: GetEnv方法中可用的jvmtiEnv类,该类使您能够通过功能和事件的概念与JVMTI层进行交互。
JVMTI功能
编写调试器的关键方面之一是要特别注意调试器代码对目标进程的影响。 这对于本机调试器库尤其重要,在该库中,代码与应用程序非常紧密地运行。 为了帮助您更好地控制调试器如何影响代码的执行,JVMTI规范引入了功能概念。
在编写调试器时,您可以提前告诉JVM您打算使用哪些API命令或事件集(即设置断点,挂起线程等)。 这使JVM可以提前为此做准备,并使您可以更好地控制调试器的运行时开销。 这种方法还使来自不同供应商的JVM能够以编程方式告诉您整个JVMTI规范中当前支持哪些API命令。
并非所有能力都是平等的 。 某些功能的性能开销相对较小。 其他有趣的,如can_generate_exception_events接收回调时异常的代码抛出,或用于接收回调时获取锁定can_generate_monitor_events,付出更高的成本。 原因是它们阻止JVM在JIT编译期间完全优化代码,并可能迫使JVM在运行时进入解释模式。
其他功能,例如can_generate_field_modification_events用于在设置目标对象字段(即设置监视)时接收通知,其成本甚至更高,从而使代码执行速度大大降低。 即使JVM支持同时加载多个本机库,HotSpot中的某些功能(例如can_suspend用于挂起和恢复线程)也只能一次声明一个库。
构建Takipi的生产调试器时,我们面临的最困难的部分之一就是提供类似的功能,而又不会产生这种开销(在以后的文章中会介绍更多)。
设置回调 。 收到一组功能后,下一步就是设置回调,JVM将调用这些回调以让您知道实际发生的时间。 这些回调中的每一个都会提供有关发生的事件的相当深的信息。 例如,对于异常回调,此信息将包括引发异常的字节码位置,线程,异常对象以及是否以及将在何处捕获该异常。
void JNICALL ExceptionCallback(jvmtiEnv *jvmti,JNIEnv *jni, jthread thread, jmethodID method,jlocation location, jobject exception,jmethodID catch_method, jlocation catch_location)
重要的是要注意,功能的开销有时分为两部分。 第一部分仅是通过启用它来完成的,因为这将导致JIT编译器以不同的方式编译事物,从而产生对代码进行调用的潜力。 第二部分是在您实际安装回调函数时出现的,因为它会使JVM在运行时选择优化程度较低的执行路径,通过该路径它可以调用您的代码,并带来解析和传递的额外开销。您有意义的数据。
断点和手表 。 您的调试器可以提供在运行时检查特定状态的熟悉功能,例如SetBreakpoint可以通知JVM暂停执行特定字节代码指令,或者SetFieldModificationWatch可以在修改字段时暂停执行。 到那时,您可以使用其他补充功能,例如GetStackTrace和GetThreadInfo来了解有关您当前在代码中的位置的更多信息并将其报告。
如下所示的大多数JVMTI函数都使用称为jmethodID和jclass的抽象句柄来引用类和方法(如果您曾经编写过Java Native Interface代码,则应该很熟悉)。 提供了诸如GetMethodName和GetClassSignature之类的附加功能,以帮助您从类的常量池中获取实际的符号名称。 然后,您可以使用它们以可读格式记录数据或将其呈现在UI中,就像我们每天在IDE中看到的那样。
附加调试器
编写调试器库后,下一步就是将其附加到JVM。 有几种方法可以做到–
1.连接JDWP 。 如果要编写基于JDWP的调试器,则需要以– agentlib:jdwp = transport = dt_socket,suspend = y,address = localhost:<port>的形式向调试对象添加启动参数以通过线路启用调试。 这些参数详细说明了调试器和目标(在本例中为套接字)之间的通信形式,以及是否以挂起模式启动调试对象。
2.附加JVMTI库 。 JVM通过传递给debuggee进程并指向您的库在磁盘上的位置的agentpath命令行参数加载JVMTI库。
另一种方法是将代理程序命令行参数附加到全局JAVA_TOOL_OPTIONS环境变量,该环境变量将由每个新JVM拾取,并且其值会自动附加到其现有参数列表中。
3.远程连接 。 附加调试器的另一种方法是使用远程附加API 。 这个简单而强大的API使您可以将代理附加到正在运行的JVM进程,而无需使用任何命令行参数启动它们。 不利的一面是您将无法使用通常需要的某些功能,例如can_generate_exception_events ,因为这些功能仅在VM启动时才需要-遗憾的是,调试器有些麻烦了。
您可以下载Takipi的生产调试器,以在此处查看其中的一些方法。
翻译自: https://www.javacodegeeks.com/2013/09/how-to-write-your-own-java-scala-debugger.html
如何编写自己的Java / Scala调试器相关推荐
- scala本地调试_如何编写自己的Java / Scala调试器
scala本地调试 在本文中,我们将探讨Java / Scala调试器的编写和工作方式. 诸如Windows的WinDbg或Linux / Unix的gdb之类的本机调试器通过操作系统直接提供给它们的 ...
- java 调试 工具_Java调试器–权威的工具列表
java 调试 工具 Java调试是一个复杂的空间. 调试器的类型很多,工具也很多. 在此页面中,我们将介绍7种类型的调试器之间的区别,并查看每个类别中的主要工具,以帮助您为正确的工作选择正确的工具. ...
- Java调试器–权威的工具列表
Java调试是一个复杂的空间. 调试器的类型很多,并且有很多工具可供选择. 在此页面中,我们将介绍7种类型的调试器之间的区别,并查看每个类别中的主要工具,以帮助您为正确的工作选择正确的工具. 以下是我 ...
- linux平台调试终端,10款Linux平台上优秀的调试器,总有一款适合你!
前言 调试器对于检测程序中的 bug 是必不可少的.有很多优秀的 Linux 调试器,可以很容易地找到应用程序中的错误.我们将在本文中介绍几款好用的调试器. 1. GNU Debugger (GDB) ...
- java开发调试定位分析工具大全
Java是一种非常强大的编程语言,自问世以来就广受欢迎.作为现今十分流行的移动平台--Android的核心语言,它大大促进了移动通信行业的发展.因此可以肯定,随着Android平台的不断扩张,Java ...
- 使用GDB命令行调试器调试C/C++程序
编译自:http://xmodulo.com/gdb-command-line-debugger.html 作者: Adrien Brochard 原创:LCTT https://linux.cn/a ...
- 性能测试脚本的编写和调试_编写自动调试器以在测试执行期间捕获异常
性能测试脚本的编写和调试 以前,我曾说过, 您总是想保留一些调试器断点作为例外 . 这有助于防止代码在不注意的情况下腐烂掉,有时掩盖了另一个问题. 如果您认真对待这一点,那么最好将此想法扩展到自动化测 ...
- Java调试器和超时
在代码中存在超时的情况下如何使用调试器. 我的调试器王国! 因此,您一直忙于编写一个项目,一切顺利,直到出现错误为止. 您可以进入开发人员的工具箱,然后拔出调试器. 很棒–您可以设置断点,可以在发生异 ...
- 编写自动调试器以在测试执行期间捕获异常
以前,我曾说过, 您总是想保留一些调试器断点作为例外 . 此帮助可防止代码在不引起注意的情况下腐烂掉-有时掩盖了另一个问题. 如果您认真对待这一点,则最好将此概念扩展到自动化测试中. 但是想出一个全面 ...
最新文章
- AI一分钟 | 小米公布Q2财报,上市以来股价振幅高达30%;俄制造商推出步行杀手机器人...
- 使用NHibernate绑定页面数据时,出现未能加载视图状态,正在向其中加载视图状态。。。。的Bug...
- 收费标准_互联网推广收费标准
- jar包不统一也会报错:Exception in thread main java.lang.NoClassDefFoundError
- MySQL的一些常用命令
- ubuntu 串口调试工具推荐_Qt开源作品3-串口调试助手
- android最新图表框架,Android中绘制图表的开源框架AChartEngine初识
- POJ3253-Fence Repair
- ADB工具安装及常用命令
- 如何对 ABAP 数据库表通过 ABAP 代码进行更新和删除操作试读版
- excel如何冻结首行或首列及首行首列同时冻结
- 编译原理(二)文法和语言、符号和符号串、文法的类型、语法树
- 都市青年图鉴:那些喊着奋斗的人,后来怎样了
- HTML中加入背景音乐
- Java创建图片并绘图
- STM32产生固定频率和占空比可变的PWM
- centos 中文乱码_StudingThinking_百度空间
- vim gvim技巧大全
- 《关于炒股有意思的几个“故事” 》
- 前端开发常用编辑器,你用过哪几个?
热门文章
- linux 安装mysql 8.0_Linux安装mysql 8.0的详细方法介绍(代码示例)
- MySQL元数据库——information_schema
- 转:微服务架构:BFF和网关是如何演化出来的?(这篇文章相当棒)
- 如何从文件系统中读取文件内容
- Ubuntu下apt-get方式Git的安装、配置和更新
- 古巴比伦乘法_古巴:为生产做准备
- stomp java客户端_Stomp-Spring服务器端的Web套接字Java客户端
- poi动态创建文档_POI创建的文档具有不同条件的灵活样式
- liskov替换原则_坚实原则:Liskov替代原则
- javaone_代理的JavaOne 2016观察