java debug 体系-jdwp
0、概述
jdwp 是Java Debug Wire Protocol 的缩写, 它定义了调试器和目标JVM之间的通信协议。 并且仅仅定义了数据传输的格式,但并没有指定具体的传输方式。
1、协议分析
jdwp-协议.jpgJDWP大致分为两个阶段:握手和应答。
1.1 握手是在传输层连接建立完成后,做的第一件事:
Debugger 发送 14 bytes 的字符串“JDWP-Handshake”到 target Java 虚拟机
Target Java 虚拟机回复“JDWP-Handshake”
1.2 握手完成,debugger 就可以向 target Java 虚拟机发送命令了。
JDWP 是通过命令(command)和回复(reply)进行通信的,这与 HTTP 有些相似。JDWP 本身是无状态的,因此对 command 出现的顺序并不受限制。
2、packet的类型
JDWP 有两种基本的包(packet)类型:命令包(command packet)和回复包(reply packet)。
Debugger 和 target Java 虚拟机都有可能发送 command packet。Debugger 通过发送 command packet 获取 target Java 虚拟机的信息以及控制程序的执行。Target Java 虚拟机通过发送 command packet 通知 debugger 某些事件的发生,如到达断点或是产生异常。
Reply packet 是用来回复 command packet 该命令是否执行成功,如果成功 reply packet 还有可能包含 command packet 请求的数据,比如当前的线程信息或者变量的值。从 target Java 虚拟机发送的事件消息是不需要回复的。
packet的结构可以见参考文件。
3、JDWP传输接口
前面提到 JDWP 的定义是与传输层独立的,但如何使 JDWP 能够无缝的使用不同的传输实现,而又无需修改 JDWP 本身的代码? JDWP 传输接口(Java Debug Wire Protocol Transport Interface)解决了这个问题。
JDWP 传输接口定义了一系列的方法用来定义 JDWP 与传输层实现之间的交互方式。首先传输层必须以动态链接库的方式实现,并且暴露一系列的标准接口供 JDWP 使用。与 JNI 和 JVMTI 类似,访问传输层也需要一个环境指针(jdwpTransport),通过这个指针可以访问传输层提供的所有方法。当 JDWP agent 被 Java 虚拟机加载后,JDWP 会根据参数去加载指定的传输层实现(Sun 的 JDK 在 Windows 提供 socket 和 share memory 两种传输方式,而在 Linux 上只有 socket 方式)。传输层实现的动态链接库实现必须暴露 jdwpTransport_OnLoad 接口,JDWP agent 在加载传输层动态链接库后会调用该接口进行传输层的初始化。
3.1 JDWP 传输层定义的接口主要分为两类:
连接管理和 I/O 操作。
连接管理接口主要负责连接的建立和关闭。一个连接为 JDWP 和 debugger 提供了可靠的数据流。Packet 被接收的顺序严格的按照被写入连接的顺序。在连接建立后,会立即进行握手操作,确保对方也在使用 JDWP。
I/O 操作接口主要是负责从传输层读写 packet。
4、命令实现机制
命令实现机制.jpg JDWP 接收和发送的包都会经过 TransportManager 进行处理。JDWP 的应用层与传输层是独立的,就在于 TransportManager 调用的是 JDWP 传输接口(Java Debug Wire Protocol Transport Interface),所以无需关心底层网络的具体传输实现。
TransportManager 的主要作用就是充当 JDWP 与外界通讯的数据包的中转站,负责将 JDWP 的命令包在接收后进行解析或是对回复包在发送前进行打包,从而使 JDWP 能够专注于应用层的实现。对于收到的命令包,TransportManager 处理后会转给 PacketDispatcher,进一步封装后会继续转到 CommandDispatcher。然后,CommandDispatcher 会根据命令中提供的命令组号和命令号创建一个具体的 CommandHandler 来处理 JDWP 命令。
其中,CommandHandler 才是真正执行 JDWP 命令的类。我们会为每个 JDWP 命令都定义一个相对应的 CommandHandler 的子类,当接收到某个命令时,就会创建处理该命令的 CommandHandler 的子类的实例来作具体的处理。
JDWP命令处理流程
JDWP命令处理流程.jpg
5、事件处理机制
在 Java 虚拟机中,我们会接触到许多事件,例如 VM 的初始化,类的装载,异常的发生,断点的触发等等。那么这些事件调试器是如何通过 JDWP 来获知的呢?下面,我们通过介绍在调试过程中断点的触发是如何实现的,来为大家揭示其中的实现机制。
首先,调试器需要发起一个断点的请求,这是通过 JDWP 的 Set 命令完成的。调试器请求的是一个断点信息.在 JDWP 的实现中,这一过程表现为:在 Set 命令中会生成一个具体的 request, JDWP 的 RequestManager 会记录这个 request(request 中会包含一些过滤条件,当事件发生时 RequestManager 会过滤掉不符合预先设定条件的事件),并通过 JVMTI 的 SetEventNotificationMode 方法使这个事件触发生效(否则事件发生时 Java 虚拟机不会报告)。
当断点发生时,Java 虚拟机就会调用 JDWP 中预先定义好的处理该事件的回调函数。回调函数的作用就是要生成一个 JDWP 端所描述的断点事件来告知调试器(Java 虚拟机只是触发了一个 JVMTI 的消息)。
由于断点的事件在调试器申请时就要求所有 Java 线程在断点触发时被 suspend,那这一步由谁来完成呢?为了解决这个问题,通过 JDWP 的 EventDispatcher 线程来帮我们 suspend 线程和发送信息。实现的过程是,让触发断点的 Java 线程来 PostEventSet,把生成的 JDWP 事件放到一个队列(eventQueue)中,然后就开始等待。由 EventDispatcher 线程来负责从队列中取出 JDWP 事件,并根据事件中的设定,来 suspend 所要求的 Java 线程并发送出该事件。
在事件触发的 Java 线程和 EventDispatcher 线程之间添加了一个同步机制,当事件发送出去后,事件触发的 Java 线程会把 JDWP 中的该事件删除,到这里,整个 JDWP 事件处理就完成了。
参考文献
3、Java ™ Debug Wire Protocol Transport Interface (jdwpTransport)