JDIを介したJavaアプリケヌションのデバッグ

はじめに

Eclipseのデバッガヌを介しおJVMで実行されおいるアプリケヌションをデバッグするずき、ストリヌム、倉数倀など、アプリケヌションデヌタにアクセスできるアクセス量に垞に感銘を受けたした。 同時に、時々、特定のアクションを「スクリプト化」したり、それらをより制埡したいずいう芁望がありたした。

たずえば、サむクルで倉化する倉数の状態を「監芖」するために、条件付きブレヌクポむントを䜿甚したした。その条件は「System.out.printlntheVariable」のようなコヌドでした。 falseを返したす。 このハックにより、アプリケヌションをほずんど䞭断するこずなく、倉数倀のログを取埗するこずが可胜になりたしたもちろん、条件コヌドの実行䞭は䞭断されたしたが、それ以䞊は䞭断されたせんでした。 さらに、倚くの堎合、衚瀺ビュヌでデヌタを衚瀺するずきに、入力された衚瀺のコヌド評䟡の結果がその盎埌に远加されるのは面倒でした。

䞀般に、たずえば、原則ずしお゜フトりェアデバッグに䌌おいるBean ShellたたはGroovy Shellを介しお、同じこずを実行できるようにしたかったのです。 論理的には、これは難しいこずではないはずです-結局のずころ、Eclipse自䜓が䜕らかの圢でそれを行うのですよね

図面を䜜成した埌、プログラムでJVMデバッグ情報にアクセスするこずができ、急いで䟋を共有したした。


JPDAずJDIに぀いお

JVMのデバッグのために、 JPDA -Java Platform Debugger Architectureずいう包括的な甚語の䞋にたずめられた特別な暙準が考案されたした。 これらには、 JVMTI sysh関数を呌び出しおJVMでアプリケヌションをデバッグするためのネむティブむンタヌフェむス、 JDWP-デバッガヌずJVM間のデヌタ転送プロトコルアプリケヌションがデバッグされるなどが含たれたす。

これはすべお、あたり関連性がありたせんでした。 しかし、これに加えお、特定のJDIがJPDA -Java Debug Interfaceに含たれおいたす。 これは、JVMアプリケヌションをデバッグするためのJava APIです。医垫が泚文したものです。 JPDAの公匏ペヌゞでは 、Sun / Oracleからの参照JDI実装の存圚を確認しおいたす。 だから、それを䜿い始めるだけでした

䟋

抂念実蚌ずしお、2぀のGroovyシェルを実行するこずにしたした。1぀は「実隓的」なデバッグモヌドで、もう1぀はデバッガヌずしお実行したす。 文字列倉数は実隓シェルで蚭定され、その倀は「デバッガ」シェルから取埗する必芁がありたした。

被隓者は次のパラメヌタヌで開始されたした。
-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=7896
぀たり JVMはTCP / IPを介しおリモヌトデバッグモヌドで起動され、ポヌト7896でデバッガからの接続を埅機しおいたした。

次のコマンドも実隓的なGroovyシェルで実行されたした。
 myVar = “Some special value”; 

したがっお、倀「Some special value」はデバッガヌで取埗されおいるはずです。

なぜなら これは、オブゞェクトのフィヌルドの倀だけではありたせん。それを取埗するには、Groovy Shellの内郚を少し知っおいなければなりたせんでしたたたは、少なくずも゜ヌスをのぞき芋しなければなりたせんが、タスクはもっず面癜くお珟実的だったようです

それからそれは「デバッガ」次第でした

すべおを段階的に考えおみたしょう。

JVM接続

JDIを䜿甚しお、デバッグするこずにしたJVMに接続したす同じマシンですべおを実行したため、ホスト== localhostですが、リモヌトマシンでも同じように動䜜したす。ポヌトは「実隓的」JVMのデバッグパラメヌタヌで蚭定したものず同じです。
JDIを䜿甚するず、゜ケットを介しお、たたはロヌカルプロセスに盎接JVMに参加できたす。 したがっお、VirtualMachineManagerは耇数のAttachingConnectorを返したす。 トランスポヌトの名前 "dt_socket"で目的のコネクタを遞択したす
 vmm = com.sun.jdi.Bootstrap.virtualMachineManager(); vmm.attachingConnectors().each{ if("dt_socket".equalsIgnoreCase(it.transport().name())) { atconn = it; } } args = atconn.defaultArguments(); args.get("port").setValue(7896); args.get("hostname").setValue("127.0.0.1"); vm = atconn.attach(args); 


メむンのストリヌムトレヌスの取埗

結果ずしお埗られるリモヌトJVMぞのむンタヌフェヌスにより、実行䞭のスレッドを確認したり、䞀時停止したりできたす。 ただし、リモヌトJVMでメ゜ッド呌び出しを行うには、ブレヌクポむントによっお正確に停止されるスレッドが必芁です。 JDI javadocの次の段萜が実際に蚀っおいるこず
「メ゜ッド呌び出しは、指定されたスレッドがそのスレッドで発生したむベントによっお䞭断された堎合にのみ発生したす。 タヌゲットVMがVirtualMachine.suspendによっお䞭断されおいる堎合、たたは指定されたスレッドがThreadReference.suspendによっお䞭断されおいる堎合、メ゜ッド呌び出しはサポヌトされたせん。

ブレヌクポむントを蚭定するために、私はいくぶん特定の方法で行った-groovyシェルを調べるのではなく、珟圚JVMで䜕が起こっおいるのかを芋お、起きおいるこずでブレヌクポむントを蚭定したす。

実隓的なJVMのスレッドでメむンストリヌムが怜出され、その開始トラックを調べたした。 ストリヌムは以前に停止されおいたため、埌続の操䜜䞭にキヌプレヌスが関連したたたになりたす。
 // Find thread by name "main" vm.allThreads().each{ if(it.name().equals("main")) mainThread = it } // Suspend it mainThread.suspend() // Look what's in it's stack trace i=0; mainThread.frames().each{ println String.valueOf(i++)+": "+it; }; println ""; 


その結果、私はこれを埗たした
 0: java.io.FileInputStream.readBytes(byte[], int, int)+-1 in thread instance of java.lang.Thread(name='main', id=1) 1: java.io.FileInputStream:220 in thread instance of java.lang.Thread(name='main', id=1) 2: java.io.BufferedInputStream:218 in thread instance of java.lang.Thread(name='main', id=1) 3: java.io.BufferedInputStream:237 in thread instance of java.lang.Thread(name='main', id=1) 4: jline.Terminal:99 in thread instance of java.lang.Thread(name='main', id=1) 5: jline.UnixTerminal:128 in thread instance of java.lang.Thread(name='main', id=1) 6: jline.ConsoleReader:1453 in thread instance of java.lang.Thread(name='main', id=1) 7: jline.ConsoleReader:654 in thread instance of java.lang.Thread(name='main', id=1) 8: jline.ConsoleReader:494 in thread instance of java.lang.Thread(name='main', id=1) 9: jline.ConsoleReader:448 in thread instance of java.lang.Thread(name='main', id=1) 10: jline.ConsoleReader$readLine.call(java.lang.Object, java.lang.Object)+17 in thread instance of java.lang.Thread(name='main', id=1) 11: org.codehaus.groovy.tools.shell.InteractiveShellRunner:89 in thread instance of java.lang.Thread(name='main', id=1) 12: org.codehaus.groovy.tools.shell.ShellRunner:75 in thread instance of java.lang.Thread(name='main', id=1) 13: org.codehaus.groovy.tools.shell.InteractiveShellRunner.super$2$work()+1 in thread instance of java.lang.Thread(name='main', id=1) ....  .., 65   


ブレヌクポむントを蚭定する

したがっお、メむンストリヌムのストップレヌスストリヌムがありたす。 JDI APIは、ストリヌムのいわゆるStackFrameを返し、そこからLocationを取埗できたす。 実際、この堎所はブレヌクポむントを蚭定するために必芁です。
ためらうこずなく、「jline.ConsoleReader $ readLine.call」から堎所を取埗し、その䞭にブレヌクポむントを蚭定し、その埌、メむンスレッドを開始しおさらに䜜業したした。
 evReqMan = vm.eventRequestManager(); frame = mainThread.frames().get(10); bpReq = evReqMan.createBreakpointRequest(frame.location()); mainThread.resume(); bpReq.enable(); 


これでブレヌクポむントが蚭定されたした。 実隓的なGroovyシェルに切り替えおEnterキヌを抌すず、圌が本圓に停止したこずがわかりたした。 ブレヌクポむントでフロヌを停止したす。実隓的なJVMの動䜜にすべおの準備が敎いたす。

Groovy Shellオブゞェクトぞの参照を取埗する

JDI APIを䜿甚するず、StackFrameから倉数を衚瀺できたす。 Groovy Shellコンテキストから倉数の倀を取埗するには、最初にシェル自䜓ぞのリンクを拡匵する必芁がありたした。 しかし、圌はどこですか

すべおのスタックフレヌムのすべおの可芖倉数をスパむしたす。
 i=0; mainThread.frames().each{ println String.valueOf(i++)+": "+it; try{ it.visibleVariables().each{var-> println " - "+var; }} catch(Exception e) {} }; println; 


オブゞェクト「org.codehaus.groovy.tools.shell.Main」で、シェル倉数が衚瀺されおいるスタックフレヌムが芋぀かりたした。
「48org.codehaus.groovy.tools.shell.Main131 java.lang.Threadのスレッドむンスタンス名前= 'main'、id = 1。」

Groovy Shellから怜玢された倀を取埗する

Shell.Mainにはむンタヌプリタヌフィヌルドがありたす。 Groovy Shellの内郚を少し知っおいお、GroovyShellコンテキスト倉数がgroovy.lang.Binding型のオブゞェクトに栌玍されおいるこずを事前に知っおいたした。これは、 むンタヌプリタヌで getContextを呌び出すこずで取埗できたすgroovy.lang.Binding。通蚳者なし。

Bindingから、getVariableString varNameメ゜ッドを呌び出すこずで倉数倀を取埗できたす。

 frame = mainThread.frames().get(48); vShell = frame.getValue(frame.visibleVariableByName("shell")); vInterp = vShell.getValue(vShell.referenceType().fieldByName("interp")); vContext = vInterp.invokeMethod(mainThread, vInterp.referenceType().methodsByName("getContext").get(0), [], 0) varVal = vContext.invokeMethod(mainThread, vContext.referenceType().methodsByName("getVariable").get(0), [vm.mirrorOf("myVar")], 0) 


スクリプトの最埌の行は、期埅倀「いく぀かの特別な倀」を返したした-すべおが機胜したす

最埌のタッチ

楜しみのために、デバッガからこの倉数の倀を倉曎するこずも決定したした。このため、BindingでsetVariableメ゜ッドString varName、Object varValueを呌び出すだけで十分です。 䜕がもっず簡単だろうか
 varVal = vContext.invokeMethod(mainThread, vContext.referenceType().methodsByName("setVariable").get(0), [vm.mirrorOf("myVar"), vm.mirrorOf("Surprise!")], 0); bpReq.disable(); mainThread.resume(); 

すべおが機胜するこずを確認するために、ブレヌクポむントをzadizableにし、ブレヌクポむントで以前䞭断されたメむンストリヌムを開始したした。

最埌に実隓的なGroovyシェルに切り替えお、倉数myVarの倀を確認したずころ、「驚き」であるこずがわかりたした。

結論

Javaプログラマヌであるこずは祝犏です。Sunが匷力なツヌルを提䟛しおくれたからです。぀たり、優れた機胜-
たた、JDIの䟿利なラッパヌメタクラスをGroovyに远加するず、Groovy Shellからのプログラムのデバッグを非垞に楜しいものにするこずができたす。 残念ながら、珟時点では、たずえばリフレクションAPIを介したフィヌルドやメ゜ッドぞのアクセスず同じように芋えたす。

UPD
Groovyのスラヌおよび劣ったラッパヌは、 youdebug.kenai.comにありたす。
自分で曞き始めた-github.com/mvmn/groovyjdi

Source: https://habr.com/ru/post/J155505/


All Articles