jdwpは、端末上でJDWP(Java Debug Wire Protocol)による接続を受け付けているプロセスのPID一覧を表示するサブコマンドである。forwardと組み合わせると、Android StudioなどのIDEを使わずにjdbでアプリへデバッガを接続できる。

デバッグ可能なプロセスの一覧を表示する

$ adb jdwp
10535

表示されるのはデバッグ可能なプロセスだけである。通常の端末(ro.debuggableが0のuserビルド)では、android:debuggable="true"でビルドしたアプリのプロセスだけが表示される。debugビルドタイプでビルドしたアプリ(Android Studioから実行したアプリなど)が該当する。

対象のアプリが起動していない場合は何も表示されない。

adb jdwpはPIDの一覧を表示した後も終了しないため、Ctrl+Cで終了する。待機中にアプリを再起動してPIDが変わっても、表示は更新されない。最新の一覧を確認したい場合はadb jdwpを実行し直す。

PIDとアプリの対応はadb shell pidofで確認できる。

$ adb shell pidof com.example.jdwpdemo
10535

jdbでデバッガを接続する

forwardのリモート側にjdwp:<PID>を指定すると、ホストのTCPポートを端末上のプロセスのJDWPへ転送できる。

$ adb forward tcp:8700 jdwp:10535
8700
$ adb forward --list
emulator-5554 tcp:8700 jdwp:10535

参考: 【adb】forwardでホスト→端末のポートフォワーディングを設定する

転送したポートへ、JDKに付属するjdbで接続する。

$ jdb -attach localhost:8700
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
Initializing jdb ...
> version
This is jdb version 21.0 (Java SE version 21.0.8)
Java Debug Interface (Reference Implementation) version 21.0
Java Debug Wire Protocol (Reference Implementation) version 1.8
JVM Debug Interface version 1.2
JVM version 8 (Dalvik, )

JVM versionにDalvikと表示され、端末上のAndroidランタイムに接続できている。現在のAndroidランタイムはARTだが、VM名は互換性のためDalvikと報告される。

ブレークポイントで止めて変数を確認する

以下のようにタップ時にonTapメソッドを呼ぶアプリで試す。

public class MainActivity extends Activity {
    private int counter = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TextView view = new TextView(this);
        view.setText("hello jdwp");
        view.setOnClickListener(v -> onTap((TextView) v));
        setContentView(view);
    }

    private void onTap(TextView view) {
        counter++;
        view.setText("tapped " + counter);
    }
}

stop inでブレークポイントを設定し、端末の画面をタップするとonTapで停止する。whereでスタックトレース、printで変数の値を確認できる。

> stop in com.example.jdwpdemo.MainActivity.onTap
Set breakpoint com.example.jdwpdemo.MainActivity.onTap
>
Breakpoint hit: "thread=main", com.example.jdwpdemo.MainActivity.onTap(), line=20 bci=0

main[1] where
  [1] com.example.jdwpdemo.MainActivity.onTap (MainActivity.java:20)
  [2] com.example.jdwpdemo.MainActivity.lambda$onCreate$0$com-example-jdwpdemo-MainActivity (MainActivity.java:15)
  [3] com.example.jdwpdemo.MainActivity$$ExternalSyntheticLambda0.onClick (D8$$SyntheticClass:0)
  [4] android.view.View.performClick (View.java:8,083)
  ...
main[1] print this.counter
 this.counter = 0
main[1] cont

contで実行を再開する。stop inで設定したブレークポイントでは、停止中にアプリの全スレッドが止まる。そのため画面も操作に反応しなくなる。

後片付け

jdbはexitで終了する。デバッグが終わったら、forwardの転送設定を削除する。

$ adb forward --remove tcp:8700

参考