Java Platform Debugger Architecture -サービス・プロバイダ・インタフェース
Java Debug Interface (JDI)のサービス・プロバイダ・インタフェースを使用すると、Connector
とTransportService
の実装を開発および配備できます。 TransportService
クラスは、Connector
で使用される基盤のトランスポート・サービスを表し、デバッガとターゲットVM間の接続の確立、およびJava Debug Wire Protocol (JDWP)パケットのトランスポートに使用されます。
JDIのサービス・プロバイダ・インタフェースに加えて、JDKにはJava Debug Wire Protocol Transport Interface (jdwpTransport)と呼ばれるトランスポート・ライブラリ・インタフェースも含まれています。 これは、トランスポート・ライブラリの開発および配置を可能にするネイティブ(C/C++)インタフェースです。 トランスポート・ライブラリは、debuggee側のJDWPエージェントによってロードされ、デバッガへの接続の確立、およびデバッガとVM間のJDWPパケットのトランスポートに使用されます。
このページでは、新しいインタフェースが使用される可能性のあるいくつかのシナリオについて説明します。 また、新しいコネクタとトランスポートの実装の開発および配置に関連するタスクの概要についても説明します。
シナリオの例
サービス・プロバイダ・インタフェースとネイティブ・トランスポート・インタフェースは、次のユーザーによって使用されることが想定されています。
- 新しい
LaunchingConnector
実装を開発する必要があるデバッガおよびツール・ベンダー、あるいは、Oracleが提供するTCP/IPおよび共有メモリー・トランスポートを超えてリモート・デバッグするための追加のトランスポート・オプションを提供する必要があるベンダー。 - TCP/IP以外のトランスポートを使用したリモート・デバッグが必要な、組込みデバイス、オペレーティング・システム、または仮想マシンのベンダー。
これらのユーザーを考慮して、新しいインタフェースを使用する場合のいくつかのシナリオについて説明します。
多くの環境、特に組込みデバイスでは、TCP/IP以外のトランスポートを使用してデバッガをターゲットVMに接続することが必要になる場合があります。 このタイプの環境では、別のトランスポートを開発したり、別のトランスポートを介したデバッグをサポートしたりするために新しいサービス・プロバイダ・インタフェースを使用できます。
debuggee側では、jdwpTransportインタフェースを実装することにより、新しいトランスポートのトランスポート・ライブラリを開発できます。 デバッガの場合は、TransportService実装を開発できます。 TransportService実装を配置すると、JDI VirtualMachineManager実装によって自動的にAttachingConnectorおよびListeningConnectorが作成され、ターゲットVMに対するリモート・デバッグが可能になります。
環境によっては、トランスポート以外のメカニズムを使用してデバッガをターゲットVMに接続することが必要になる場合があります。 たとえば、クラッシュ・ダンプまたはハング・プロセスに(読取り専用で)接続するためにデバッガが使用される場合があります。
これらの例の場合は、
AttachingConnector
実装を開発できます。AttachingConnector
実装は、com.sun.jdi.connect.AttachingConnectors
を拡張し、配備されると、VirtualMachineManagerのattachingConnectors()
メソッドによって返される接続コネクタのリストに表示されます。多くの環境では、ターゲットVMは、きわめて独自の方法で起動されます。 こうした環境では、Oracleが提供する
com.sun.jdi.CommandLineLaunch
LaunchingConnector
によって使用されるConnector引数または起動メカニズムが十分でない場合があります。 この場合、オペレーティング・システムまたは仮想マシンのベンダーが、ターゲットVMを起動できる独自のLaunchingConnector
を開発することがあります。 この新しいLaunchingConnector
は、デバッグするターゲットVMの構成に使用される適切なConnector
引数を持ちます。 これを配備すると、VirtualMachineManager
オブジェクトのlaunchingConnectors()
メソッドによって返されるConnectors
のリストに表示されます。別の例は、Integrated Development Environment (IDE)の実装者がOracle以外のトランスポートでのデバッグをサポートする場合の企業環境において発生します。 たとえば、TLS/SSLを使用するセキュアな接続を介したデバッグのオプションをIDEで提供する場合があります。
この例では、IDEの実装者がjdwpTransportインタフェースを使用してトランスポート・ライブラリを開発します。 この結果、debuggeeでは新しいトランスポートの使用が可能になります。 デバッガ側では、IDEの実装者に選択肢があります。 1つのオプションは、TransportService実装を開発および配置することです。 このオプションでは、リモート・デバッグに新しいトランスポートを使用できます。
あるいは、IDEの実装者が、トランスポートをカプセル化するConnector実装を作成することを決定する可能性があります。 このオプションは、IDEの実装者が新しいConnector引数を追加するときに適しています。 たとえば、セキュリティ保護されたトランスポートを使う場合、IDEの実装者が、セキュアな接続を構成するために必要なキーストア、パスフレーズなどのオプションを指定するConnector引数を持つConnectorを使用する場合があります。 新しいConnectorが適している場合、IDEの実装者は、デバッグ対象側のトランスポート・ライブラリとデバッガ側のConnectorを開発します。 Connectorのタイプは、実装者が選択できます。1つの例として、デバッグ対象を起動し、デバッガとデバッグ対象の間のセキュアな接続を確立するLaunchingConnectorがあります。
Connectorの開発
Connector
の開発には、LaunchingConnector
、AttachingConnector
またはListeningConnector
の具体的な実装の作成が含まれます。
すべてのConnector
実装には、すべてのConnector
メソッドの実装に加えて、引数を取らないパブリック・コンストラクタが必要です。 コンストラクタは、初期化中にVirtualMachineManager
によってコールされます。 コンストラクタは無操作の場合や、トランスポート・サービスのロードなどの初期化タスクを実行する場合があります。 コンストラクタはチェックされた例外をスローしないため、初期化中の問題は、Error
またはその他の未チェックの例外としてスローする必要があります。
TransportService
を使用するには、Connectors
は必要ありません。 Connectors
の中には、トランスポート(「シナリオの例」セクションに、クラッシュ・ダンプまたはハングしたプロセスに接続するAttachingConnectors
の例を示します)以外のメカニズムを使用してターゲットVMに接続できるものがあります。 TransportService
実装を使用するConnectors
の場合、Connector
はTransportService
実装を直接参照するか、実行時に実装をロードできます。 Oracleが提供するトランスポートを使用しようとするConnectorは、次のようなコードを使用してトランスポート・サービスをロードするようにしてください。
try {
Class<?> c = Class.forName("com.sun.tools.jdi.SocketTransportService");
ts = (TransportService)c.newInstance();
} catch (Exception x) {
throw new Error(x);
}
Java SE実装には、Oracleのソケットまたは共有メモリー・トランスポート・サービスの実装を含める必要はありません。前述の例では、トランスポート・サービスが存在しない場合にError
がスローされます。
Connector
のタイプがわかっている場合、実装の開発時に次のアイテムに注意してください:
Connector.Arguments
のリストは慎重に考慮する必要があります。 一部のConnector
実装では、デフォルトの引数の構造と解析が実装のコードの大部分である場合があります。- 使用するトランスポートに名前を付けるには、
Connector
が必要です。 実装で既存のTransportService
を使用する場合、推奨されるトランスポート名は基礎となるTransportService
の名前です。 つまり、Connector
のtransport()
実装は、name()
メソッドがトランスポートの名前を表すjava.lang.String
を返すTransport
を返します。 - ほとんどの
Connector
実装では、ターゲットVMに対してConnection
が確立されます。Connector
のlaunch
、attach
またはaccept
メソッドが確立されると、デバッガにVirtualMachine
インスタンスが返されます。VirtualMachine
ミラーの作成を容易にするために、VirtualMachineManager
にはVirtualMachine
を作成するメソッドがあります。 次のコード・フラグメントは、このメソッドの使用例を示しています。
VirtualMachine vm = Bootstrap.virtualMachineManager().createVirtualMachine(conn);
\
\
The `VirtualMachineManager` also involves another form of the
`createVirtualMachine` method for use by `LaunchingConnector` instances.
The other form allows the `LaunchingConnector` to specify the
`java.lang.Process` that represents the debuggee. See the specification for
`com.sun.jdi.VirtualMachineManager` for further details.
Connectorの配備
Connector
をデプロイするには、Connector
実装のクラスを、Connector
の完全修飾クラス名をリストするサービス構成ファイルとともにjarファイルにパッケージ化する必要があります。 その後、jarファイルはシステム・クラス・ローダーから可視の場所に配置されます。
jarファイルに含める必要があるサービス構成ファイルの名前は、META-INF/services/com.sun.jdi.connect.Connector
です。 このファイルには、jarファイルに含まれるConnector
の完全修飾クラス名がリストされます。 複数のConnector
実装を同じjarファイルに含めることができ、その場合、各Connector
のクラス名が別の行にリストされます。
SimpleLaunchingConnector
という起動コネクタをデプロイするとします。 これをデプロイするには、次のようなファイルMETA-INF/services/com.sun.jdi.connect.Connector
を作成します:
# A very simple launching connector
SimpleLaunchingConnector
このサービス構成ファイルは、コネクタの実装を構成するクラスと一緒にjarファイルにパッケージ化されます。
jar cf SimpleLaunchingConnector.jar \
META-INF/services/com.sun.jdi.connect.Connector \
SimpleLaunchingConnector.class
次に、jarファイルはシステム・クラス・ローダーから可視の場所にコピーされます。
デプロイすると、デバッガの再起動時にデバッガによってConnector
が検索されます。 つまり、SimpleLaunchingConnector
は、VirtualMachineManager
のallConnectors()
メソッドによって返されるConnectors
のリストに含まれます。 また、これは起動コネクタであるため、launchingConnectors()
メソッドによって返される起動コネクタのリストにも表示されます。
TransportServiceの開発
トランスポート・サービスを開発するには、次の2つのコンポーネントを開発する必要があります。
com.sun.jdi.connect.spi.TransportService
の具体的な実装。jdwpTransport
インタフェースを実装するデバッグ側の共有ライブラリ。
トランスポート・サービスの開発には、トランスポートと基盤になる通信プロトコルについての高度な知識が必要です。 トランスポート・サービスは、JDWPを基盤となる通信プロトコルにバインドします。 提供するサービスは信頼性が高く、JDWPパケットはデバッガとdebuggeeの間で重複したりデータが失われることなく、交換されます。 パケットを信頼性の高い方法で交換しなければならないことを考えると、トランスポート・サービスは、基盤となる通信プロトコルが提供するサービスを超えるプロトコル・サポートを提供するべきです。 たとえば、処理されていない信頼性の低いシリアル接続を介したデバッグが必要な場合、トランスポート・サービスの実装者は、エラーの検出と回復を実装に組み込んで、デバッガとdebuggeeの間でJDWPパケットを信頼性の高い方法で転送できるようにする必要があります。
トランスポートと基盤の通信プロトコルの詳細を理解したら、次のステップは、次のことを考慮します。
TransportService
のcapabilities()
メソッドは、トランスポート・サービスを示すTransportService.Capabilities
を返します'の機能。 したがって、トランスポート・サービスの実装者は、次のことを考慮する必要があります。- トランスポートが1つのリスナー・アドレスへの複数の同時接続をサポートできるかどうか
- 接続時、ハンドシェーク時、または接続の確立の待機時にタイム・アウトを効果的に実装できるかどうか
- ネイティブ・トランスポート・ライブラリは、1つのJDWPエージェントによって使用されることもあれば、複数のJDWP (またはほかの)エージェントによって同時に使用されることもあります。 トランスポートが複数の環境をサポートしている場合、
jdwpTransport_OnLoad
関数を呼び出すたびに、新しい環境ポインタが返されます。 トランスポートが単一の環境のみをサポートする場合、2番目以降にjdwpTransport_OnLoad
をコールすると、エラーが返されます。 したがって、トランスポート・ライブラリの実装者は、ライブラリの実装が1つの環境または複数の環境のどちらをサポートするかを決定する必要があります。 - アドレス(接続エンドポイント)は、
Strings
で表されます。 したがって、実装者は、アドレスのすべてのコンポーネントをString
にエンコードできるように、アドレス・スキームを設計する必要があります。 たとえば、シリアル・ポートのアドレスと構成は、次のようにエンコードされます。
/dev/ttya;9600,1
- 適切な
ListenKey
実装を設計します。TransportService
を使用して複数の異なるアドレスをリスニングできるため、各"listener"を一意に識別するためにリスニング・キーが同時に使用されます。 TransportService
の適切な名前と説明を決定します。 起動時に、VirtualMachineManager
によって、トランスポート・サービスをカプセル化するためのアタッチおよびリスニング・コネクタが作成されます。 これらのConnectors
の名前と説明は、トランスポート・サービスの名前と説明に基づきます - たとえば、TransportService
のname()
メソッドが"Serial"を返す場合、VirtualMachineManager
によって作成されたConnectors
は"SerialAttach"および"SerialListen"という名前になります。
前述の解決後、TransportService
の作成には、com.sun.jdi.connect.spi.TransportService
の拡張と実装の提供が含まれます。 attachメソッドおよびacceptメソッドは、com.sun.jdi.connect.spi.Connection
のインスタンスを返すため、デバッガがJDWPパケットをデバッグ先と交換できるように、Connection
の実装が必要です。
ネイティブ・トランスポート・ライブラリを開発するには、jdwpTransport仕様に記載されている各関数を実装する必要があります。 関数のプロトタイプと定義は、${java_home}/include/jdwpTransport.h
で定義されています。
トランスポート・ライブラリの実装者は、関数実装をコンパイルし、ダイナミック・ライブラリ(またはこれに相当するもの)にリンクします。 ライブラリは、トランスポート・ライブラリのロード時にJDWPエージェントが呼び出す関数jdwpTransport_OnLoad
関数をエクスポートします。 一部の組込み環境は、動的リンクをサポートしていません。そういった環境では、トランスポート・ライブラリに静的にリンクしなければならない場合があります。 その場合、ライブラリのロードは行われませんが、jdwpTransport_OnLoad
関数は、トランスポート・ライブラリを初期化して環境ポインタを取得するためにコールされます。
TransportServiceの配備
TransportService
は、Connector
と同様の方法でデプロイされます。 TransportService
をデプロイするには、TransportService
実装のクラスを、TransportService
の完全修飾クラス名をリストするサービス構成ファイルとともにjarファイルにパッケージ化する必要があります。 その後、jarファイルはシステム・クラス・ローダーから可視の場所に配置されます。
jarファイルに含める必要があるサービス構成ファイルの名前は、META-INF/services/com.sun.jdi.connect.spi.TransportService
です。 Connector
デプロイメントによると、jarファイルに複数の実装が含まれている場合に、構成ファイルで複数のトランスポート・サービス実装のクラス名をリストできます。
トランスポート・サービスcom.sun.SerialTransportService
の場合、サービス構成ファイルは次のようになります :
# Serial line transport
com.foo.SerialTransportService
このサービス構成ファイルは、実装を構成するクラスと一緒にjarファイルにパッケージ化されます。 この例では、実装に多数のクラスが含まれると仮定します。
jar cf SerialTransportService.jar \
META-INF/services/com.sun.jdi.connect.spi.TransportService \
com/foo/SerialTransportService.class \
com/foo/SerialConnection.class \
com/foo/SerialCapabilities.clas \
com/foo/SerialIO.class \
com/foo/SerialProtocol.class
Connectors
のデプロイメントに従って、jarファイルがシステム・クラス・ローダーに表示されるロケーションにコピーされます。
TransportService
は、ネイティブ・メソッドを持つか、ネイティブ・ライブラリを必要とする他のAPIに依存する場合があります。 その場合、ネイティブ・ライブラリは、System.loadLibrary
を使用してロードできるロケーションである必要があります。
ネイティブ・トランスポート・ライブラリは、JDWPエージェントによってロードされます。 このため、ネイティブ・トランスポート・ライブラリは、オペレーティング・システムの通常の実行時ライブラリ検索パス上に存在する必要があります。 たとえば、Linuxシステムでは、LD_LIBRARY_PATH
環境変数で指定された検索パスにある必要があります。