scala本地调试_如何编写自己的Java / Scala调试器
scala本地調(diào)試
在本文中,我們將探討Java / Scala調(diào)試器的編寫和工作方式。 諸如Windows的WinDbg或Linux / Unix的gdb之類的本機(jī)調(diào)試器通過操作系統(tǒng)直接提供給它們的鉤子來獲取其功能,以監(jiān)視和操縱外部進(jìn)程的狀態(tài)。 JVM充當(dāng)OS之上的抽象層,它提供了自己的獨立體系結(jié)構(gòu)來調(diào)試字節(jié)碼。
該框架及其API是完全開放的,有文檔的且可擴(kuò)展的,這意味著您可以輕松編寫自己的調(diào)試器。 該框架的當(dāng)前設(shè)計由兩個主要部分組成-JDWP協(xié)議和JVMTI API層。 每種方法都有其各自的優(yōu)點和用例,它們可以最佳地發(fā)揮作用。
JDWP協(xié)議
Java調(diào)試器有線協(xié)議通常用于在網(wǎng)絡(luò)上通過二進(jìn)制消息在調(diào)試器和被調(diào)試進(jìn)程之間傳遞請求和接收事件(例如線程狀態(tài)或異常的更改)。 該體系結(jié)構(gòu)背后的概念是在兩者之間建立盡可能多的隔離。 這是為了減少讓調(diào)試器在運行目標(biāo)代碼時更改其執(zhí)行的海森堡效應(yīng)(物理學(xué)家維爾納,而不是友好的Meth Cooking Walt )。
從目標(biāo)進(jìn)程中刪除盡可能多的調(diào)試器邏輯也可以幫助確保已調(diào)試VM狀態(tài)的更改(例如,“停止世界” GC或OutOfMemoryErrors)不會影響調(diào)試器本身。 為了使事情變得更容易,JDK附帶了JDI (Java調(diào)試器接口),它提供了協(xié)議的完整調(diào)試器端實現(xiàn),并具有連接,分離,監(jiān)視和操縱目標(biāo)VM的狀態(tài)的能力。
例如,該協(xié)議與Eclipse的調(diào)試器使用的協(xié)議相同。 如果查看在IDE調(diào)試時傳遞給Java進(jìn)程的命令行參數(shù),您會注意到Eclipse傳遞給它的其他參數(shù)(-agentlib:jdwp = transport = dt_socket,…)來啟用JVM調(diào)試,并且還會建立發(fā)送請求和事件的端口。
JVMTI API
現(xiàn)代JVM調(diào)試器體系結(jié)構(gòu)中的第二個關(guān)鍵組件是一組本機(jī)API,涵蓋了與JVM操作相關(guān)的廣泛領(lǐng)域,稱為JVM工具接口 (即JVMTI)。 與JDWP不同,JVMTI被設(shè)計為一組C / C ++ API,并且具有JVM動態(tài)加載使用API??提供的命令的預(yù)編譯庫(例如.dll或.so)的機(jī)制。
這種方法與JDWP的不同之處在于,它實際上在目標(biāo)進(jìn)程內(nèi)部執(zhí)行調(diào)試器。 這增加了調(diào)試器在性能和穩(wěn)定性方面影響應(yīng)用程序代碼的可能性。 但是,關(guān)鍵優(yōu)勢在于能夠以近乎實時的方式直接與JVM交互。
由于JVMTI提供了一組功能強(qiáng)大的低級API集,所以我認(rèn)為有必要進(jìn)一步深入研究并解釋其工作原理,以及可以使用哪些出色的功能。 可通過JDK隨附的jvmti.h獲得API標(biāo)頭。
編寫調(diào)試器庫
編寫自己的調(diào)試器需要使用C ++創(chuàng)建本機(jī)OS庫。 在這種情況下,您的“主要”功能看起來像–
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void*)當(dāng)調(diào)試器代理由JVM加載時,該函數(shù)將由JVM調(diào)用。 傳遞給您的日益重要的JavaVM指針將為您提供與JVM對話所需的一切。 它引入了JavaVM :: GetEnv方法中可用的jvmtiEnv類,使您能夠通過功能和事件的概念與JVMTI層進(jìn)行交互。
JVMTI功能
編寫調(diào)試器的關(guān)鍵方面之一是要特別注意調(diào)試器代碼對目標(biāo)進(jìn)程的影響。 這對于本機(jī)調(diào)試器庫尤其重要,在該庫中,您的代碼與應(yīng)用程序非常緊密地運行。 為了幫助您更好地控制調(diào)試器如何影響代碼的執(zhí)行,JVMTI規(guī)范引入了功能的概念。
在編寫調(diào)試器時,您可以提前告訴JVM您打算使用哪些API命令或事件集(即設(shè)置斷點,掛起線程等)。 這使JVM可以提前為此做準(zhǔn)備,并且使您可以更好地控制調(diào)試器的運行時開銷。 這種方法還使來自不同供應(yīng)商的JVM能夠以編程方式告訴您整個JVMTI規(guī)范中當(dāng)前支持哪些API命令。
并非所有能力都是平等的 。 某些功能的性能開銷相對較小。 其他有趣的事件,例如can_generate_exception_events用于在代碼中引發(fā)異常時接收回調(diào),或can_generate_monitor_events用于在獲取鎖時接收回調(diào),則成本更高。 原因是它們阻止JVM在JIT編譯過程中完全優(yōu)化代碼,并可能迫使JVM在運行時進(jìn)入解釋模式。
其他功能,例如can_generate_field_modification_events用于在設(shè)置目標(biāo)對象字段(即設(shè)置監(jiān)視)時接收通知,其成本甚至更高,從而使代碼執(zhí)行速度大大降低。 即使JVM支持同時加載多個本機(jī)庫,HotSpot中的某些功能(例如can_suspend用于掛起和恢復(fù)線程)也只能一次聲明一個庫。
在構(gòu)建Takipi的生產(chǎn)調(diào)試器時,我們面臨的最困難的部分之一就是提供類似的功能而又不會產(chǎn)生這種開銷(在以后的文章中會介紹更多)。
設(shè)置回調(diào) 。 收到一系列功能后,下一步就是設(shè)置回調(diào),JVM將調(diào)用這些回調(diào)以讓您知道實際發(fā)生的時間。 這些回調(diào)中的每一個都會提供有關(guān)已發(fā)生事件的相當(dāng)深入的信息。 例如,對于異常回調(diào),此信息將包括引發(fā)異常的字節(jié)碼位置,線程,異常對象以及是否以及將在何處捕獲該異常。
void JNICALL ExceptionCallback(jvmtiEnv *jvmti,JNIEnv *jni, jthread thread, jmethodID method,jlocation location, jobject exception,jmethodID catch_method, jlocation catch_location)重要的是要注意,功能的開銷有時分為兩部分。 第一部分僅是通過啟用它來完成的,因為這將導(dǎo)致JIT編譯器以不同的方式編譯事物,從而產(chǎn)生對代碼進(jìn)行調(diào)用的潛力。 第二部分是在您實際安裝回調(diào)函數(shù)時出現(xiàn)的,因為它會導(dǎo)致JVM在運行時選擇優(yōu)化程度較低的執(zhí)行路徑,通過這些路徑,它可以調(diào)用您的代碼,并帶來解析和傳遞的額外開銷。您有意義的數(shù)據(jù)。
斷點和手表 。 您的調(diào)試器可以提供在運行時檢查特定狀態(tài)的熟悉功能,例如SetBreakpoint以通知JVM在特定字節(jié)代碼指令處掛起執(zhí)行,或者SetFieldModificationWatch在字段被修改時暫停執(zhí)行。 到那時,您可以使用其他補(bǔ)充功能,例如GetStackTrace和GetThreadInfo來了解有關(guān)您在代碼中當(dāng)前位置的更多信息并將其報告。
如下所示的大多數(shù)JVMTI函數(shù)都使用稱為jmethodID和jclass的抽象句柄來引用類和方法(如果您曾經(jīng)編寫過Java Native Interface代碼,則應(yīng)該很熟悉)。 提供了諸如GetMethodName和GetClassSignature之類的附加功能,以幫助您從類的常量池中獲取實際的符號名稱。 然后,您可以使用它們以可讀格式記錄數(shù)據(jù)或?qū)⑵涑尸F(xiàn)在UI中,就像我們每天在IDE中看到的那樣。
附加調(diào)試器
編寫調(diào)試器庫后,下一步就是將其附加到JVM。 有幾種方法可以做到–
1.連接JDWP 。 如果要編寫基于JDWP的調(diào)試器,則需要以– agentlib:jdwp = transport = dt_socket,suspend = y,address = localhost:<port>的形式向調(diào)試對象添加啟動參數(shù)以通過線路啟用調(diào)試。 這些參數(shù)詳細(xì)說明了調(diào)試器和目標(biāo)(在本例中為套接字)之間的通信形式,以及是否以掛起模式啟動調(diào)試對象。
2.附加JVMTI庫 。 JVM通過傳遞給debuggee進(jìn)程并指向您的庫在磁盤上的位置的agentpath命令行參數(shù)加載JVMTI庫。
另一種方法是將代理程序命令行參數(shù)附加到全局JAVA_TOOL_OPTIONS環(huán)境變量,該環(huán)境變量將由每個新JVM拾取,并且其值會自動附加到其現(xiàn)有參數(shù)列表中。
3.遠(yuǎn)程連接 。 附加調(diào)試器的另一種方法是使用遠(yuǎn)程附加API 。 這個簡單而強(qiáng)大的API使您可以將代理附加到正在運行的JVM進(jìn)程,而無需使用任何命令行參數(shù)啟動它們。 不利的一面是您將無法使用通常需要的某些功能,例如can_generate_exception_events ,因為這些功能僅在VM啟動時才需要-不幸的是,調(diào)試器有些麻煩了。
您可以下載Takipi的生產(chǎn)調(diào)試器,以在此處查看其中的一些方法。
翻譯自: https://www.javacodegeeks.com/2013/09/how-to-write-your-own-java-scala-debugger.html
scala本地調(diào)試
總結(jié)
以上是生活随笔為你收集整理的scala本地调试_如何编写自己的Java / Scala调试器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Apache Spark Job的剖析
- 下一篇: java 正则表达式使用_如何用正则表达