JPDA(Java Platform Debugger Architecture)简介
JPDA是为桌面系统开发环境调试而设计的一套调试体系,它由三个模块组成:
JVM TI(Java Virtual Machine Tools Interface)
定义了虚拟机必须提供的用于调试的服务。JVM TI由虚拟机实现,我们把虚拟机端/被调试进程端称为JPDA后端,而把调试进程端称为JPDA前端。
JDWP(Java Debug Wire Protocol)
定义了被调试进程与调试器终端进程之间信息和请求的传输格式。在某些虚拟机中,JVM TI的实现存在一些问题,它们直接在虚拟机中实现了JDWP。在客户端,用Java编程语言以外的语言编写的应用程序可能不是使用JDI的最佳选择。
JDI(Java Debug Interface)
从用户代码级别定义了消息和请求。JDI提供了一个纯净的Java程序接口,用以调试Java应用程序。在JPDA中,JDI是调试器进程与被调试进程间的远程视图。它由前端实现。
JPDA工作流程
访问任何一个接口有两类活动:请求、事件。
-
请求
由调试器端发起,包括查询信息、设置远程虚拟机/程序的状态、设置调试状态。 -
事件
由被调试端发起,指示远程虚拟机/程序的状态。
一、查询信息
让我们以用户在IDE的栈视图中选择一个本地变量,并请求它的值的例子来说明JPDA的工作流程:
1、IDE使用JDI获取值,它将调用getValue方法:
theStackFrame.getValue(theLocalVariable)
其中,theStackFrame是com.sun.jdi.StackFrame,theLocalVariable是com.sun.jdi.LocalVariable,都位于JDK lib目录下的tools.jar。
前端通过通信通道(比如Socket)向运行在后端的被调试进程发送查询请求。具体实现时,会按照JDWP的标准将发送的信息转化为字节流。
2、后端解析字节流,然后通过JVM TI将查询发送给虚拟机。假设我们所请求的值是整型,那么我们将调用如下JVM TI方法:
error = jvmti->GetLocalInt(frame, slot, &intValue);
然后后端通过Socket返回响应报文,响应报文中便包含了intValue的值。当然,响应报文也会按照JDWP的标准格式化。
3、前端解析响应报文,返回getValue的值,继而IDE显示这个值。
二、设置调试状态
我们以设置断点为例。
设置调试状态的流程与查询信息的流程基本一致。首先,我们需要调用JDI相应的方法,然后按照JDWP的标准发送命令,但JVM TI方法的调用有些不同。
此外,前端和后端做的不仅仅是来回推送数据,它们跟踪和调度活动并转换、过滤和缓存信息,因此断点请求与值查询处理完全不同,但是通信序列将是相同的。
那么当一个被调试的应用断点被触发时将会发生什么呢?这个时候事件就发挥它的作用了。虚拟机通过JVM TI发送事件。比如说,发起事件处理函数传递断点信息:
static void Breakpoint(jvmtiEnv *jvmti_env,
JNIEnv* jni_env, jthread thread,
jmethodID method, jlocation location) {
...
}
这个后端方法将会开启一个活动链,用来过滤是否是它感兴趣的事件消息。当捕获到它感兴趣的消息时,会将消息入队,然后按照JDWP标准定义的断点事件格式,通过Socket发送消息给前端。前端解码并处理事件,最终将生成对应的JDI事件。以断点事件为例,将会暴露成com.sun.jdi.event.BreakpointEvent。此时,IDE通过将事件移除事件队列的方法来获取它:
theEventQueue.remove()
theEventQueue为com.sun.jdi.event.EventQueue。IDE将会利用JDI进行多次查询来合理更新断点状态。
参考资料: