JPDA (Java Platform Debugger Architecture) ist die Abkürzung für Java Platform Debugger Architecture. Über die von JPDA bereitgestellte API können Entwickler einfach und flexibel Java-Debugging-Anwendungen erstellen. .
JPDA besteht hauptsächlich aus drei Teilen: Java Virtual Machine Tool Interface (JVMTI), Java Debug Wire Protocol (JDWP) und Java Debug Interface (JDI).
Java-Programme werden alle auf der Java Virtual Machine ausgeführt. Wenn wir ein Java-Programm debuggen möchten, müssen wir tatsächlich den aktuellen Ausführungsstatus von der Java Virtual Machine anfordern, bestimmte Anweisungen an die virtuelle Maschine erteilen, einige Rückrufe festlegen usw. Dann ist das Java-Debugging-System ein vollständiger Satz von Tools und Schnittstellen zum Debuggen der virtuellen Maschine.
1: Der Editor fungiert als Client- und Serverprogramm, um eine Socket-Verbindung über den offengelegten Überwachungsport herzustellen.
2: Der IDE-Client erstellt die Haltepunktposition und überträgt die Haltepunktereignis über die JDI-Schnittstelle Wird an die VM auf dem Server (Programm) übergeben und die VM ruft suspend auf, um die VM anzuhalten
3: Nachdem die VM angehalten wurde, werden die VM-Informationen, die der Client abrufen muss, an den Client zurückgegeben. Nach der Rückkehr nimmt die VM ihren Betriebszustand wieder auf.
4: Nachdem der Client die von der VM zurückgegebenen Informationen erhalten hat, können sie auf verschiedene Arten angezeigt werden. (3) Architektursystem: JPDA definiert ein vollständiges und unabhängiges System, das besteht aus drei relativ unabhängigen Schichten und legt den Interaktionsmodus zwischen den drei fest oder definiert die Schnittstelle für ihre Kommunikation.
Diese drei Module unterteilen den Debugging-Prozess in mehrere natürliche Konzepte: Debugger (Debugger), Debugee (Debuggee) und den Kommunikator zwischen ihnen.
Der Debugger läuft auf der Java Virtual Machine, die wir debuggen möchten. Er kann die Informationen der aktuellen virtuellen Maschine über die JVMTI-Standardschnittstelle überwachen. Über diese Schnittstellen können Benutzer die Debugging-Schnittstellen definieren Die debuggte virtuelle Maschine sendet Debugging-Befehle, und der Debugger akzeptiert die Debugging-Ergebnisse und zeigt sie an.
Das JDWP-Kommunikationsprotokoll wird zur Übertragung von Debugging-Befehlen und Debugging-Ergebnissen verwendet. Es verbindet den Debugger und den Debugger während des Debuggens. Alle Befehle werden in JDWP-Befehlspakete gekapselt und über die Transportschicht an das zu debuggende Objekt gesendet. Nachdem das zu debuggende Objekt das JDWP-Befehlspaket empfangen hat, analysiert es den Befehl und wandelt ihn in einen JVMTI-Aufruf um, der auf dem zu debuggenden Objekt ausgeführt wird.
Eine ähnliche Situation besteht darin, dass JVMTI die laufenden Ergebnisse in die Form von JDWP-Datenpaketen umwandelt, die Ergebnisse an den Debugger sendet und sie an den JDI-Aufruf zurückgibt. Der Debugger-Entwickler ruft Daten ab und gibt Anweisungen über JDI aus.
Wie in der Abbildung oben gezeigt, besteht JPDA aus drei Schichten:JVM TI
– Java VM-Tool-Schnittstelle. Definiert die von der VM bereitgestellten Debugging-Dienste.
JDWP
– Java-Debugging-Kommunikationsprotokoll. Definiert die Kommunikation zwischen dem Debugee und dem Debugger-Prozess. JVM TI
- Java VM 工具接口。定义 VM 提供的调试服务。
JDWP
- Java 调试通信协议。定义被调试者和调试器进程之间的通信。
JDI
JDI
– Java-Debugging-Schnittstelle. Definiert eine High-Level-Java-Sprachschnittstelle, die Tool-Entwickler problemlos zum Schreiben von Remote-Debugger-Anwendungen verwenden können.
Über die JPDA-Schnittstelle können wir unsere eigenen Debugging-Tools entwickeln. Über die von diesen JPDAs bereitgestellten Schnittstellen und Protokolle können Debugger-Entwickler Java-Debugging-Anwendungen entsprechend den Anforderungen bestimmter Entwickler erweitern und anpassen. Die zuvor erwähnten IDE-Debugging-Tools basieren alle auf dem JPDA-System. Der einzige Unterschied besteht darin, dass sie möglicherweise unterschiedliche grafische Schnittstellen bieten und über einige unterschiedliche benutzerdefinierte Funktionen verfügen. Darüber hinaus sollten wir beachten, dass es sich bei JPDA um eine Reihe von Standards handelt und jede JDK-Implementierung diesen Standard erfüllen muss. Daher sind über JPDA entwickelte Debugging-Tools von Natur aus plattformübergreifend, unabhängig von der Implementierung virtueller Maschinen und unabhängig von der JDK-Version usw . Transplantationsvorteile, daher basieren die meisten Debugging-Tools auf diesem System. 2. Beispiel für Remote-Debugging【1】Erstellen Sie ein SpringBoot-WEB-Projekt. Die SpringBoot-Version, die wir derzeit verwenden, ist 2.3.0.RELEASE. Die entsprechende Tomcat-Version ist 9.X. Packen Sie das SpringBoot-Projekt und legen Sie den Anwendungsport auf 9999 fest. Die Bereitstellung dieses Programms auf einem Linux-Server, unabhängig davon, ob ein JAR-Paket oder Docker verwendet wird, hat nichts mit Remote-Debugging zu tun. 【3】Die Code-Referenz des Bereitstellungsprogramms lautet wie folgt. Dabei handelt es sich um eine einfache Anforderung zum Verarbeiten von Ausdruckinformationen: als Socket-Kommunikationsport für Remote-Debugging
Wenn es sich um ein normales Webprojekt handelt, das unter Tomcat bereitgestellt wird, beachten Sie bitte Folgendes: Weniger als Tomcat9-Version
tomcat 中 bin/catalina.sh 中增加 CATALINA_OPTS=‘-server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=18006’
如下图所示:
大于等于 tomcat9 版本
tomcat 中 bin/catalina.sh 中的 JPDA_ADDRESS=“localhost:8000” 这一句中的localhost修改为0.0.0.0(允许所有ip连接到8000端口,而不仅是本地)8000是端口,端口号可以任意修改成没有占用的即可
如下图所示:
【5】测试部署的程序正常后,下面构建客户端远程调试,当前以IDEA工具作为客户端
参考:
【1】-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8888
【2】Host:远程服务器地址
【3】Port:远程服务器开放的调试通信端口,非应用端口
测试接口:http://XXX:9999/test。注意本地代码需要和远程部署程序一致。
通过上图可以看到客户端设置断点已经生效,其中在客户端执行了一个调试输出,这个是自定义输出的内容服务器程序并没有,在执行后右侧的服务器控制台日志输出了该信息,因此远程Debug是正常通信和处理的。
(一)调试参数详解
-Xdebug
:启用调试特性
-Xrunjdwp
:在目标 VM 中加载 JDWP 实现。它通过传输和 JDWP 协议与独立的调试器应用程序通信。下面介绍一些特定的子选项
从 Java V5 开始,您可以使用 -agentlib:jdwp 选项,而不是 -Xdebug 和 -Xrunjdwp。如果连接到VM版本低于V5的情况下,只能使用 -Xdebug 和 -Xrunjdwp 选项。下面简单描述 -Xrunjdwp 子选项。
-Djava.compiler=NONE
: 禁止 JIT 编译器的加载
transport
: 传输方式,有 socket 和 shared memory 两种,我们通常使用 socket(套接字)传输,但是在 Windows 平台上也可以使用shared memory(共享内存)传输。
server(y/n)
: VM 是否需要作为调试服务器执行
address
: 调试服务器的端口号,客户端用来连接服务器的端口号
suspend(y/n)
:值是 y 或者 n,若为 y,启动时候自己程序的 VM 将会暂停(挂起),直到客户端进行连接,若为 n,自己程序的 VM 不会挂起
(1)被调试程序
创建一个SpringBoot的WEB项目,提供一个简单的测试接口,并在测试方法中提供一些方法参数变量和局部变量作为后面的调试测试用。
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController public class DebuggerApplication { public static void main(String[] args) { SpringApplication.run(DebuggerApplication.class, args); } @GetMapping("/test") public String test(String name){ System.out.println("进入方法"); int var=100; System.out.println(name); System.out.println(var); System.out.println("方法结束"); return "OK"; } }
项目启动配置参考,需要启用Debug配置
(2)自定义调试器代码
开发调试器需要JNI工具支持,JDI操作的API工具在tools.jar中 ,需要在 CLASSPATH 中添加/lib/tools.jar
import com.sun.jdi.*; import com.sun.jdi.connect.AttachingConnector; import com.sun.jdi.connect.Connector; import com.sun.jdi.event.*; import com.sun.jdi.request.BreakpointRequest; import com.sun.jdi.request.EventRequest; import com.sun.jdi.request.EventRequestManager; import com.sun.tools.jdi.SocketAttachingConnector; import java.util.List; import java.util.Map; /** * 通过JNI工具测试Debug * @author zhangyu * @date 2022/2/20 */ public class TestDebugVirtualMachine { private static VirtualMachine vm; public static void main(String[] args) throws Exception { //获取SocketAttachingConnector,连接其它JVM称之为附加(attach)操作 VirtualMachineManager vmm = Bootstrap.virtualMachineManager(); List<AttachingConnector> connectors = vmm.attachingConnectors(); SocketAttachingConnector sac = null; for(AttachingConnector ac : connectors) { if(ac instanceof SocketAttachingConnector) { sac = (SocketAttachingConnector) ac; } } assert sac != null; //设置好主机地址,端口信息 Map<String, Connector.Argument> arguments = sac.defaultArguments(); Connector.Argument hostArg = arguments.get("hostname"); Connector.Argument portArg = arguments.get("port"); hostArg.setValue("127.0.0.1"); portArg.setValue(String.valueOf(8800)); //进行连接 vm = sac.attach(arguments); //相应的请求调用通过requestManager来完成 EventRequestManager eventRequestManager = vm.eventRequestManager(); //创建一个代码判断,因此需要获取相应的类,以及具体的断点位置,即相应的代码行。 ClassType clazz = (ClassType) vm.classesByName("com.zy.debugger.DebuggerApplication").get(0); //设置断点代码位置 Location location = clazz.locationsOfLine(22).get(0); //创建新断点并设置阻塞模式为线程阻塞,即只有当前线程被阻塞 BreakpointRequest breakpointRequest = eventRequestManager.createBreakpointRequest(location); //设置阻塞并启动 breakpointRequest.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); breakpointRequest.enable(); //获取vm的事件队列 EventQueue eventQueue = vm.eventQueue(); while(true) { //不断地读取事件并处理断点记录事件 EventSet eventSet = eventQueue.remove(); EventIterator eventIterator = eventSet.eventIterator(); while(eventIterator.hasNext()) { Event event = eventIterator.next(); execute(event); } //将相应线程resume,表示继续运行 eventSet.resume(); } } /** * 处理监听到事件 * @author zhangyu * @date 2022/2/20 */ public static void execute(Event event) throws Exception { //获取的event为一个抽象的事件记录,可以通过类型判断转型为具体的事件,这里我们转型为BreakpointEvent,即断点记录, BreakpointEvent breakpointEvent = (BreakpointEvent) event; //并通过断点处的线程拿到线程帧,进而获取相应的变量信息,并打印记录。 ThreadReference threadReference = breakpointEvent.thread(); StackFrame stackFrame = threadReference.frame(0); List<LocalVariable> localVariables = stackFrame.visibleVariables(); //输出当前线程栈帧保存的变量数据 localVariables.forEach(t -> { Value value = stackFrame.getValue(t); System.out.println("local->" + value.type() + "," + value.getClass() + "," + value); }); } }
(3)代码分析
【1】通过Bootstrap.virtualMachineManager();获取连接器,客户端即通过相应的connector进行连接,配置服务器程序ip地址和端口,连接后获取对应服务器的VM信息。
定位目标debug的类文件,可通过遍历获取的类集合并结合VirtualMachine获取类信息实现
【3】对目标类代码特定位置设置并启用断点
【4】记录断点信息,阻塞服务器线程,并根据对应事件获取相应的信息
【5】执行event.resume释放断点,服务器程序继续运行
(4)运行测试
启动 SpringBoot 的 web 项目,也就是服务器程序。启动调试器代码时使用debug模式,并在该位置查看所获取的信息,同时避免直接释放断点。
【2】Setzen Sie den Haltepunkt auf Zeile 22 der DebuggerApplication-Klasse.
【3】Testen Sie die Schnittstelle nach dem Start. Sie können feststellen, dass die Serverprogrammkonsole die folgenden Ergebnisse ausgibt. Zeile 22 wurde noch nicht ausgeführt.
【4】Zu diesem Zeitpunkt wird das Debugger-Programm beobachtet. Sie können sehen, dass die Daten des Serverprogramm-Stack-Frames abgerufen wurden
Das obige ist der detaillierte Inhalt vonWas ist das Prinzip des Java-Plattform-Debugging-Systems?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!