Java

JavaTM Platform Debugger Architecture
概要

ホームページ
Java Platform Debugger Architecture (JPDA) は、2 つのインタフェース (JVMTI と JDI)、1 つのプロトコル (JDWP)、およびそれらのコンポーネントを結び合わせる 2 つのソフトウェアコンポーネント (バックエンドとフロントエンド) で構成されています。JVMTI は、JVMDI に置き換わるものとして J2SE 5.0 に導入された新しいインタフェースです。それらの構成には、次のようないくつかの目的があります。

背景

「Java Platform Debugger Architecture」を参照してください。

モジュール性

ここでは、JPDA のモジュール化された構造の詳細を説明します。それぞれの説明では、標準的な JPDA の使用法を取り上げます。また、リファレンス実装について説明し、インタフェースの代替実装およびクライアントについても説明します。

JVMTI のモジュール性

Java Virtual Machine Tool Interface (JVMTI) では、仮想マシン (VM) で動作する Java プログラミング言語のアプリケーションをデバッグできるようにするため、その VM から提供される機能について記述されています。JPDA では、JVMTI は VM によって実装され、クライアントは JPDA のバックエンドになります。JPDA のリファレンス実装では、JVMTI は Java HotSpot VM によって実装され、クライアントはバックエンドのリファレンス実装 (JDK に付属の、jdwp.so や jdwp.dll などのネイティブ共用ライブラリとして提供される) になります。

Java HotSpot VM 以外の多くの VM も、JVMTI を実装しています。バックエンドのリファレンス実装は、ほかのいくつかのプラットフォームに移植されました。さらに、バックエンド以外にも JVMTI のクライアントがあります。もっとも有名なのは、ネイティブコードと Java プログラミング言語コードの両方のデバッグを可能にするアプリケーションエージェント (ネイティブレベルの制御と情報を必要とする) です。バックエンドのクリーンルーム実装を意識する必要はありません。そのようなバックエンドを作成することも可能ですが、相当な労力を必要とします。

JDWP のモジュール性

Java Debug Wire Protocol (JDWP) は、debuggee とデバッガの間でやり取りされるデバッグ情報および要求の形式を記述しています。JPDA では、フロントエンド (デバッガのプロセス内にある) とバックエンド (debuggee のプロセス内にある) との間に通信チャネルがあります。そのチャネル上を流れるデータの形式が、JDWP によって記述されています。JPDA のリファレンス実装では、バックエンドのリファレンス実装 (前述) がこのチャネルの debuggee 側を提供し、フロントエンドのリファレンス実装 (tools.jar にある JDK の Java プログラミング言語コンポーネント) がこのチャネルのデバッガ側を提供します。

一部の VM では、JVMTI の実装に問題があります。そのような VM では、JDWP が直接実装されます。クライアント側では、Java プログラミング言語で作成されていないアプリケーションは、JDI を使用するアプリケーションとして不適格なことがあります。アプリケーションによっては、JDWP のクライアントとして実装することもあります。

JDI のモジュール性

Java Debug Interface (JDI) は、Java プログラミング言語のアプリケーションをデバッグするための pure Java プログラミング言語インタフェースを提供します。JPDA では、JDI は、デバッガのプロセスから見た debuggee プロセスの仮想マシンのリモートビューです。JDI は、フロントエンド (前述) によって実装されますが、そのクライアントになるのはデバッガ役のアプリケーション (IDE、デバッガ、トレースツール、モニターツールなど) です。

JDI は、アプリケーションの静的なビューを提供するようにシステムによって実装されることがあります。また、JDWP のフロントエンドとはまったく違う機構で情報を収集したり VM を制御したりするように実装されることもあります。

動作の概要

これまでは、各インタフェースを使用するさまざまな方法について説明してきました。このあとは、標準的な JPDA が全体としてどのように動作するかを見ていきます。説明の中では、個々の呼び出しやコードの詳細について、実例を取り上げます。そのような実例は、理解できなくても問題ありません。実例を具体的にするために紹介してあるだけです。

各インタフェースを橋渡しするのは、要求とイベントという 2 つのアクティビティです。要求は、デバッガ側から出されるもので、情報の照会、リモート側の VM やアプリケーションの状態変更の設定、およびデバッグ状態の設定が含まれています。イベントは、debuggee 側から出されるもので、リモート側の VM やアプリケーションの状態変化を示しています。

1 つの実例を調べてみましょう。IDE のスタック表示でユーザが局所変数をクリックし、その値を要求したとします。IDE は、JDI を使ってその値を取得します。具体的には、getValue メソッドを呼び出します。次に例を示します。

    theStackFrame.getValue(theLocalVariable)
ここで、theStackFramecom.sun.jdi.StackFrame であり、theLocalVariablecom.sun.jdi.LocalVariable です。

次に、フロントエンドは、この要求を通信チャネル (たとえば、ソケット) 経由で、debuggee プロセスが動作しているバックエンドに送ります。そのとき、フロントエンドは、その要求を JDWP に準拠したバイトストリームの形式に変換します。具体的に言うと、フロントエンドは GetValues コマンド (バイト値 1) をStackFrame コマンドセット (バイト値 16) で送り、そのあとにスレッド ID、フレーム ID などが続きます。

バックエンドは、そのバイトストリームを解析し、JVMTI を介して VM に照会を送ります。具体的には、要求された値が整数だとすると、次のような JVMTI 関数の呼び出しを実行します。

    error = jvmti->GetLocalInt(frame, slot, &intValue);
バックエンドは、ソケット経由で応答パケットを返送します。そのパケットには intValue の値が入っており、JDWP に準拠したデータ形式になっています。フロントエンドは、応答パケットを解析し、その値を getValue メソッド呼び出しの値として返します。最後に、IDE は、返された値を表示します。

デバッグ状態を変更する要求も、同様の方法で処理されます。たとえば、ブレークポイントを設定するという要求は、同様のステップで処理されます。もちろん、呼び出される JDI メソッドや、送信される JDWP コマンドや、呼び出される JVMTI 関数は違います。さらに、フロントエンドとバックエンドは、単にデータをやり取りする以上のことを行います。アクティビティを追跡およびスケジューリングし、情報を変換、フィルタリング、およびキャッシュします。したがって、ブレークポイントを設定する要求は、値を取得する照会とはかなり違った仕方で処理されますが、通信の手順は同じです。

デバッグしているアプリケーションがこのブレークポイントに達すると、何が起こるのでしょうか。今度は、イベントの出番になります。仮想マシンは、JVMTI インタフェースを介してイベントを送ります。具体的には、仮想マシンは、イベント処理関数を呼び出して、ブレークポイントを渡します。

バックエンドは、イベント処理関数を次のように設定しています。

static void Breakpoint(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread, jmethodID method, jlocation location) { ...
このバックエンド関数は、関心のあるイベントをフィルタリングし、そのイベントをキューに入れ、ブレークポイントイベント用に定義された JDWP 形式のソケットを介してそのイベントを送信するという、一連のアクティビティを開始します。フロントエンドは、そのイベントをデコードして処理し、最終的には JDI イベントを生成します。具体的には、JDI イベントは、com.sun.tools.jdi.event.BreakpointEvent として公開されます。その後、IDE は、そのイベントをイベントキューから取り出して取得します。
    theEventQueue.remove()
ここで、theEventQueue は、com.sun.jdi.event.EventQueue です。IDE は、JDI を介して多くの照会呼び出しを実行することにより、表示を更新すると予想されます。

移殖

仮想マシンの各実装には、それぞれ独自の JVMTI 実装が必要です。JVMTI の実装では、VM のデータ構造に深く踏み込む必要があり、イベントを取得するために VM 実装の中にフックを設定する必要があります。JVMTI サポートのない VM に JVMDT を追加するのは、かなりの作業になります。VM の複雑さと、実装する JVMTI のオプション機能の量に応じて、3 〜 12 か月の作業になると考えられます。JVMTI サポートが組み込まれている VM を新しいプラットフォームに移殖する作業は、VM の JVMTI 以外の部分の移殖が中心になります。JVMTI 部分に必要な付加的な作業量は、比較的少ないものです。

バックエンドのリファレンス実装を新しいプラットフォームに移すには、多くの場合、ソースにわずかの変更 (数行のみ) を加えるか、ソースをまったく変更せずに、再コンパイルするだけで済みます。同じプラットフォーム上で新しい VM を使用する場合は、バックエンドのバイナリコードは多くの場合そのまま動作します。ただし、それは Java プログラミング言語のコードではないため、内容を理解することはできません。このドキュメントではライセンスの問題には触れていないので、注意してください。

フロントエンドの実装は Java プログラミング言語で作成されているため、どのプラットフォームまたは VM でも動作します。ただし、一部のシステムでは、コネクタコードの機能の一部を拡張する必要がある場合もあります。たとえば、フロントエンドのリファレンス実装に含まれている起動ツールでは、仮想マシンが J2SE の規則を使って起動されることを前提としています。JDI のユーザが自分たちの希望に合わせて起動ツールの構文を決めることもできますが、一般に、デバッガアプリケーションでは、その構文が JDI 実装の側で決められていると想定します。また、もし別の種類の通信チャネル (たとえば、シリアル接続) が必要な場合は、その機能も追加する必要があります。結局、その追加を可能にするには、JDI に SPI を追加することになります。ただし、現在のところ、それにはソースを変更する必要があります。


Copyright © 2004 Sun Microsystems, Inc.All Rights Reserved. 

コメントの送付先: java-debugger@sun.com 

Sun