GDBを使用したネイティブ実行可能ファイルのデバッグ
生成されたネイティブ実行可能ファイルは、最小限のシンボル情報で高度に最適化されたコードであるため、デバッグが困難になります。これを解決するには、ビルド時にデバッグ情報を結果のバイナリに埋め込みます。この情報は、マシン・コードを解釈して元のJavaメソッドに戻す方法をデバッガに正確に伝えます。
このガイドでは、標準のLinux GNUデバッガ(GDB)を使用してネイティブ実行可能ファイルをデバッグする方法について学習します。
ノート: GDBを使用したネイティブ・イメージのデバッグは現在、macOSの初期サポートでLinux上で機能します。この機能は試験段階です。
デモの実行
デバッグ情報を使用してネイティブ実行可能ファイルをビルドするには、アプリケーションのコンパイル時にjavac
に-g
コマンドライン・オプションを指定してから、native-image
ビルダーに指定します。これにより、ソース・レベルのデバッグが有効になり、デバッガ(GDB)はマシン命令をJavaファイル内の特定のソース行と関連付けます。
前提条件
- Linux AMD64
- GDB 10.1以上
GDBを使用してネイティブ実行可能ファイルのデバッグをテストするステップに従います。次のワークフローは、GDB 10.1を使用するLinux OSで動作することが確認されています。
- 次のコードをGDBDemo.javaという名前のファイルに保存します。
public class GDBDemo { static long fieldUsed = 1000; public static void main( String[] args ) { if (args.length > 0) { int n = -1; try { n = Integer.parseInt(args[0]); } catch (NumberFormatException ex) { System.out.println(args[0] + " is not a number!"); } if (n < 0) { System.out.println(args[0] + " is negative."); } double f = factorial(n); System.out.println(n + "! = " + f); } if (false) neverCalledMethod(); StringBuilder text = new StringBuilder(); text.append("Hello World from GraalVM Native Image and GDB in Java.\n"); System.out.println(text.toString()); } static void neverCalledMethod() { System.out.println("This method is unreachable and will not be included in the native executable."); } static double factorial(int n) { if (n == 0) { return 1; } if (n >= fieldUsed) { return Double.POSITIVE_INFINITY; } double f = 1; while (n > 1) { f *= n--; } return f; } }
-
これをコンパイルし、デバッグ情報を使用してネイティブ実行可能ファイルを生成します:
$JAVA_HOME/bin/javac -g GDBDemo.java
native-image -g -O0 GDBDemo
-g
オプションは、デバッグ情報を生成するようにnative-image
に指示します。結果のネイティブ実行可能ファイルには、GDBによって認識される形式のデバッグ・レコードが含まれます。-O0
を渡して、コンパイラの最適化を実行しないことを指定することもできます。すべての最適化の無効化は必須ではありませんが、一般的にはデバッグ・エクスペリエンスが向上します。 -
デバッガを起動し、ネイティブ実行可能ファイルを実行します:
gdb ./gdbdemo
gdb
プロンプトが開きます。 -
ブレークポイントを設定します:
breakpoint <java method>
と入力してブレークポイントを設定し、run <arg>
と入力してネイティブ実行可能ファイルを実行します。ブレークポイントは、ファイルと行、またはメソッド名で構成できます。デバッグ・セッションの例を次に示します。$ gdb ./gdbdemo GNU gdb (GDB) 10.2 Copyright (C) 2021 Free Software Foundation, Inc. ... Reading symbols from ./gdbdemo... Reading symbols from /dev/gdbdemo.debug... (gdb) info func ::main All functions matching regular expression "::main": File GDBDemo.java: 5: void GDBDemo::main(java.lang.String[]*); (gdb) b ::factorial Breakpoint 1 at 0x2d000: file GDBDemo.java, line 32. (gdb) run 42 Starting program: /dev/gdbdemo 42 Thread 1 "gdbdemo" hit Breakpoint 1, GDBDemo::factorial (n=42) at GDBDemo.java:32 32 if (n == 0) { (gdb) info args n = 42 (gdb) step 35 if (n >= fieldUsed) { (gdb) next 38 double f = 1; (gdb) next 39 while (n > 1) { (gdb) info locals f = 1 (gdb) ...
ネイティブ実行可能ファイルがセグメンテーション違反の場合、スタック全体のバックトレースを出力できます(bt
)。
デバッガは、マシン命令をバイナリからJavaファイル内の特定のソース行に戻します。コンパイル済メソッド内のシングルステップ実行には、インライン化されたコードのファイルおよび行番号情報が含まれることに注意してください。GDBでは、同じコンパイル済メソッドの処理中でもファイルを切り替えることができます。
通常のデバッグ・アクションのほとんどは、GDBでサポートされています:
- シングルステップ実行(関数コールへのステップインとステップオーバーの両方を含む)
- スタック・バックトレース(インライン化されたコードの詳細を示すフレームは含まない)
- プリミティブ値の出力
- Javaオブジェクトの構造化、フィールド単位、出力
- 異なる汎用性レベルでのオブジェクトのキャストおよび出力
- パス式を使用したオブジェクト・ネットワーク経由でのアクセス
- メソッドおよび静的フィールド・データの名前による参照
デバッグ情報の生成は、Javaプログラムを同等のC++プログラムとしてモデル化することによって実装されます。GDBは主にC (およびC++)のデバッグ用に設計されているため、Javaアプリケーションのデバッグ時に考慮すべき点がいくつかあります。ネイティブ・イメージのデバッグのサポートの詳細は、リファレンス・ドキュメントを参照してください。