天天看點

遠端調試方式IntelliJ遠端調試使用JDI進行線上程式斷點資訊記錄

要讓遠端伺服器運作的代碼支援遠端調試,則啟動的時候必須加上特定的JVM參數,這些參數是:

其中的${debug_port}是使用者自定義的,為debug端口,本例以5555端口為例。

transport: 表示資訊傳遞的連接配接方式, 其中,dt_socket是指用SOCKET模式,另有dt_shmem指用共享記憶體方式,其中,dt_shmem隻适用于Windows平台。

server: server=y 表示是監聽其他debug client端的請求

address 表示等待調試的連接配接網絡端口

suspend 表示是否在啟動目标虛拟機後挂起虛拟機,如果要調試啟動過程,請使用y

參數說明:

address為遠端調試的端口号,目前我們伺服器上8412是一個開放的端口(staging環境已驗證)

transport=dt_socket 使用socket方式進行連接配接,還可以使用其他通信方式如(dt_shmem 共享記憶體)

suspend=n JVM監聽address端口傳來的信号時不挂起JVM中運作的程序

示例方式:

IntelliJ遠端調試

使用JDI進行線上程式斷點資訊記錄

打開Intellij IDEA,在頂部靠右的地方選擇”Edit Configurations…”,進去之後點選+号,選擇”Remote”,按照下圖的隻是填寫紅框内的内容,其中host為遠端代碼運作的機器的ip/hostname,port為上一步指定的debug_port,本例是5555。然後點選Apply,最後點選OK即可

遠端調試方式IntelliJ遠端調試使用JDI進行線上程式斷點資訊記錄

現在在上一步選擇”Edit Configurations…”的下拉框的位置選擇上一步建立的remote的名字,然後點選右邊的debug按鈕(長的像臭蟲那個),看控制台日志,如果出現類似“Connected to the target VM, address: ‘xx.xx.xx.xx:5555’, transport: ‘socket’”的字樣,就表示連接配接成功過了。

遠端調試方式IntelliJ遠端調試使用JDI進行線上程式斷點資訊記錄

遠端debug模式已經開啟,現在可以在需要調試的代碼中打斷點了,比如:

遠端調試方式IntelliJ遠端調試使用JDI進行線上程式斷點資訊記錄

如圖中所示,如果斷點内有√,則表示選取的斷點正确。

現在在本地發送一個到遠端伺服器的請求,看本地控制台的bug界面,劃到debugger這個标簽,可以看到目前遠端服務的内部狀态(各種變量)已經全部顯示出來了,并且在剛才設定了斷點的地方,也顯示了該行的變量值。

遠端調試方式IntelliJ遠端調試使用JDI進行線上程式斷點資訊記錄
遠端調試方式IntelliJ遠端調試使用JDI進行線上程式斷點資訊記錄

java的整個調試體系為JDPA,Oracle提供了進階的jdi接口以友善使用java來連接配接調試程式進行相應的調試。這樣,隻需要調用相應的java接口,就能進行打斷點,記錄斷點,然後繼續運作,清除斷點這樣基本的斷點調試手法了。

連接配接其它JVM稱之為附加(attach)操作,目前實作中有2種,如果是本地JVM,則通過Process的方式即可,如果是遠端,則需要通過socket的方式才能進行連接配接。首先是server端需要開啟調試agent,并且指定相應的端口,如下啟動指令所示:

-Xdebug -agentlib:jdwp=transport=dt_socket,address=1234,server=y,suspend=n

上面的參數表示監聽指定端口(1234),并且當存在斷點時并不主動阻塞。舊的JVM也有使用Xrunjdwp參數的,但不再被建議使用.

用戶端即通過相應的connector進行連接配接,如下的socket連接配接方式:

即設定好主機位址,端口資訊,然後即可以進行連接配接。

相應的請求調用通過requestManager來完成,由剛才成功連接配接上的vm(VirtualMachine)來擷取。由于這裡是建立一個代碼判斷,是以需要擷取相應的類,以及具體的斷點位置,即相應的代碼行。 

要擷取代碼行,則要求源檔案(class)中必須存在lineCode資訊,即通過在抛出異常時,在異常資訊中所指定的行數。這些資訊預設情況下編譯時會自動輸出,但使用編譯開關-g:none時,則不會輸出。如果沒有line資訊,則不能建立相應的斷點資訊。建立資訊如下所示:

以上即拿到類A1的第15行的執行段(可能有多個,一行代碼可能有多個調用),并設定阻塞模式為線程阻塞,即隻有目前線程被阻塞。最終啟用之,即成功建立新斷點。這裡必須将其設定為阻塞,否則将斷點記錄産生時,由于目前線程繼續運作了,将不能拿到相應的資訊(表示為産生IncompatibleThreadStateException)

接下來就是從vm的事件隊列中,不斷地讀取事件并處理斷點記錄事件即可。由于斷點産生時,server端線程被阻塞,是以必須盡可能快地處理斷相應的,以避免出現業務阻塞。标準的事件循環如下所示:

上面在事件記錄處理完之後,必須将相應線程resume,表示繼續運作。

這裡擷取的event為一個抽象的事件記錄,可以通過類型判斷轉型為具體的事件,這裡我們轉型為BreakpointEvent,即斷點記錄,并通過斷點處的線程拿到線程幀,進而擷取相應的變量資訊,并列印記錄。如下所示:

上面的value也是抽象形式,也可以通過類型轉換以列印不同類型的對象。

必須調用相應的event.resume

如果記錄的資訊又足夠(如記錄了半天的資料了),則可能通過requestManager.deleteEventRequest調用相應的斷點,或者調用deleteAllBreakpoints删除所有斷點資訊。或者通過vm.dispose直接中斷相應的調試連接配接即可。