JAVA リモートデバッグ

## JDB (Java デバッガー)

**JDB (Java Debugger)** は、Java プログラムをデバッグするコマンドラインベースのツールです。Java Development Kit (JDK) に含まれており、Java Debug Interface (JDI) を介して動作します。

#### 主な機能とコマンド
1. **プログラムの実行と接続**
   - `jdb <class>`: クラスをロードしてデバッグを開始します。
   - `jdb -attach <host>:<port>`: リモート JVM に接続してデバッグを行います。

2. **ブレークポイントの設定**
   - `stop at <class>:<line>`: 特定の行にブレークポイントを設定します。
   - `stop in <class>.<method>`: 特定のメソッドにブレークポイントを設定します。

3. **実行制御**
   - `run`: プログラムの実行を開始します。
   - `cont`: プログラムの実行を続行します。
   - `step`: 次の行に移動します。
   - `next`: 現在の行を実行して次の行に移動します。

4. **情報の確認**
   - `print <expression>`: 式の値を出力します。
   - `locals`: 現在のローカル変数を出力します。
   - `dump <object>`: オブジェクトのすべてのフィールド値を出力します。

5. **スタックトレース**
   - `where`: 現在のスレッドのスタックトレースを出力します。

6. **スレッド制御**
   - `threads`: 実行中のすべてのスレッドを一覧表示します。
   - `thread <thread_id>`: 特定のスレッドに切り替えます。

#### 使用例
```sh
jdb MyClass
stop in MyClass.main
run
print myVariable
step
next
cont
```

### JDBで使用されるプロトコル

**Java Debug Wire Protocol (JDWP)** は、JDB で使用される主要なプロトコルです。JDWP はデバッガーと JVM の間の通信を担当し、ブレークポイント、ステップ実行、変数の確認などのデバッグ作業をサポートします。

### 類似技術

1. **GDB (GNU Debugger)**
   - 主に C/C++ プログラムのデバッグに使用されます。
   - 多様なブレークポイントの設定、スタックトレースの確認、変数値の変更などをサポートします。
   - GDB サーバーとクライアントモデルを通じてリモートデバッグも可能です。

2. **LLDB**
   - LLVM プロジェクトのデバッガーで、C、C++、Objective-C、および Swift プログラムをデバッグします。
   - GDB と似た機能を提供し、Xcode と統合されて使用されます。

3. **WinDbg**
   - Windows オペレーティングシステムで主に使用されるデバッガーです。
   - カーネルモードおよびユーザーモードのデバッグをサポートし、Windows ドライバーおよびアプリケーションのデバッグに主に使用されます。

4. **Eclipse Debugger**
   - Eclipse IDE で提供されるデバッガーで、Java を含む複数の言語をデバッグできます。
   - グラフィカルユーザーインターフェースを提供し、使いやすいです。

5. **Visual Studio Debugger**
   - Visual Studio IDE で提供されるデバッガーで、主に C#、C++、VB.NET などの言語をデバッグします。
   - 強力な機能と直感的なユーザーインターフェースを提供します。

JDB は JDWP を使用して Java プログラムをデバッグするコマンドラインツールです。類似のデバッグツールには GDB、LLDB、WinDbg、Eclipse Debugger、Visual Studio Debugger などがあります。これらのツールは、それぞれの環境と言語に合わせて多様なデバッグ機能を提供します。

## VSCODEでのリモートサーバーデバッグの例

まず、以下のようなソースを Ubuntu ベースのサーバーでホスティングすると仮定します。
（実際には WSL2 ベースでテストを行いました。）

``` java
import java.util.concurrent.CountDownLatch;

public class OurApplication {
    private static String staticString = "Static String";
    private String instanceString;
    private static final CountDownLatch latch = new CountDownLatch(1);

    public static void main(String[] args) {
        for (int i = 0; i < 1_000_000_000; i++) {
            OurApplication app = new OurApplication(i);
            System.out.println(app.instanceString);
        }

        System.out.println("Press Ctrl+C to exit...");
        try {
            latch.await(); // CountDownLatchを利用して無限待機
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public OurApplication(int index) {
        this.instanceString = buildInstanceString(index);
    }

    public String buildInstanceString(int number) {
        return number + ". Instance String !";
    }
}
```

次に DockerFile を作成する際には、jdwp を使用するように引数を追加してください。

``` dockerfile
# ベースイメージの選択
FROM openjdk:11-jdk

# アプリケーションのコピー
COPY . /app
WORKDIR /app

# アプリケーションのコンパイル
RUN javac OurApplication.java

# デバッグポートの公開
EXPOSE 5005

# ENTRYPOINTとCMDの組み合わせで引数を渡す
ENTRYPOINT ["java", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", "OurApplication"]
```

次に、Docker イメージをビルドして Docker でホスティングします。

```
# docker ビルド
docker build -t java-debug .

# docker コンテナ実行
docker run -d -p 5005:5005 java-debug
```

コンテナを実行し、Windows 環境で OurApplication.java ソースがあるという前提で .vscode/launch.json にリモートデバッグ設定を追加してください。
（ローカルサーバー環境と開発サーバー環境が分離されていることを前提にしています。Windows 環境はここではローカル環境です。）

``` json
{
    // IntelliSenseを使用して、利用可能な属性について学びます。
    // 既存の属性の説明を表示するには、ホバーします。
    // 詳細については、https://go.microsoft.com/fwlink/?linkid=830387 をご覧ください。
    "version": "0.2.0",
    "configurations": [
        {
            "type": "java",
            "name": "OurApplication",
            "request": "launch",
            "mainClass": "OurApplication",
            "projectName": "remote_89c5668d"
        },
        {
            "type": "java",
            "name": "Attach to Remote Program",
            "request": "attach",
            "hostName": "localhost", 
            "port": 5005
        }
    ]
}
```

そしてデバッグモードで実行すると、launch.json の設定により Ubuntu サーバーとデバッグ接続が確立されます。

![01.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824062/f093dc3e-c30d-6862-c4ae-a35aabd6b4ca.png)


VARIABLES では現在のスタックで使用される変数情報を表示でき、WATCH にはユーザーが確認したい変数の情報を確認できます。
ただし、ここでは変数の変更はできません。

![02.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824062/2de10a04-df1d-4674-0eb0-b77395de8439.png)


下部の DEBUG CONSOLE では変数の変更が可能です。ここでは手動でユーザーが希望する値を設定することができます。

![03.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824062/712b8936-e391-a178-d7ed-34db69481ffa.png)

変更された値の出力は TERMINAL タブで確認できます。
![04.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824062/2ae1011b-8264-182e-00d0-f5bf00f9aa76.png)

![05.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824062/7800aa1d-394b-402f-7d59-2e1d7ecc9984.png)

この方法を使用して、リモートの運用環境でデバッグすることが可能です。