gdbによるGraalVMネイティブイメージのデバッグ

Red Hat で Solution Architect として OpenJDK を担当している伊藤ちひろ(@chiroito)です。

この記事は、Red Hat Developerのブログ記事、Debugging GraalVM-native images using gdb | Red Hat Developer の翻訳記事です。


https://developers.redhat.com/sites/default/files/styles/article_feature/public/blog/2020/06/java-debug-coffee-955468_1280-1.jpeg?itok=tiIV3t3Q

GraalVMプロジェクトには、他の機能に加えて、GraalVMネイティブイメージと呼ばれるコンポーネントが含まれています。GraalVMネイティブイメージは、Javaアプリケーションをパッケージした自己完結型のスタンドアロン実行ファイルとして提供することをサポートしており、一般的にJavaネイティブイメージと呼ばれています。ネイティブイメージは、同じアプリケーションを従来の方法でJVM上で実行するのに比べて、フットプリントが小さく、起動時間が早いことが多いです。これは、短時間で実行されるアプリケーションや、小規模なコンテナベースのサービスには有利です。トレードオフとして、長時間動作するプログラムではピーク時のパフォーマンスが低下し、大量の生存データを持つプログラムではガベージコレクションのオーバーヘッドと遅延が高くなります。

特に、Quarkusをベースにしたアプリケーションの代わりのバックエンドへ届ける選択肢として、GraalVMネイティブイメージに興味があります。Javaチームは、QuarkusがGraalVMネイティブイメージとうまく統合されるように努力してきました。その過程で、配信されたネイティブイメージをデバッグできるかどうかが、重要な使い勝手の問題であることがわかりました。

もちろん、これは主に開発上の問題ではありません。アプリケーションをデバッグするための大変な作業のほとんどは、開発やテストの際にJVM上で行えます。しかし、デプロイされたネイティブイメージの動作が異なる場合、どうすればよいかという問題が常にあります。このようなことはあってはならないことですが、アプリケーションのエラーやビルドの設定に問題があるために起こることもあります。まれに、アプリケーション・コードやJDKランタイム・コードをネイティブ・マシン・コードにコンパイルする際に、ネイティブ・コンパイル・プロセスによってもたらされた不一致が原因で、デプロイされたネイティブ・イメージの動作が異なる場合があります。

デバッグを難しくしているのは、生成されたネイティブ・イメージが、最小限のシンボル情報で高度に最適化されたコードであることです。オリジナルのJavaメソッドのコードの多くはインライン化されているため、特定の命令を元のJavaメソッドに関連付けることは非常に困難であり、Javaソースファイル内の特定のコード行に関連付けることはできません。最高の最適化レベルでコンパイルされ、シンボル情報を持たないCやC++のプログラムと同じくらい、デバッグが困難です。

もちろん、C/C++コンパイラは、バイナリにdebuginfoを埋め込むことで、この問題を解決しています。この情報は、デバッガに対して、生成されたコードをどのように解釈し、元のソースと関連付けるかを正確に伝えます。そこで私は最近、LinuxイメージにDWARFデバッグ情報を追加することで、GraalVMネイティブイメージの問題を解決することにしました。これで、Linuxの標準的なデバッガであるgdbを使って、効果的にソースレベルのデバッグができるようになりました。Windowsバイナリをデバッグするための同等のソリューションは、現在開発中です。

デバッガは、イメージにコンパイルされた Java クラスとメソッドを知ることができます。ブレークポイントは、メソッドの入口やメソッド内の特定の行に置けます。デバッガは、ネイティブ・イメージ内のマシン命令と、Javaファイル内の特定のソース行を関連付けられます。メソッドのコードを一行ごとに停止してステップ実行し、コールスタックを表示できます。命令がインライン化されたメソッドに属するかどうかも把握しており、コードを読み進める際には、外側のコンパイルされたメソッドのソース行とインライン化されたソースコードの間を行き来します。

このデバッグ機能は、最新のGraalVMリリースに搭載されました。最近、私はQuarkusチャンネルビデオプレゼンテーションをアップロードし、実装された内容をより正確に説明しています。このビデオには、小さなJavaプログラムをベースにしたデモが含まれており、デバッグ情報を含むネイティブイメージを構築し、それをgdbで実行/デバッグする方法を示しています。デモを楽しんでいただければ幸いです。また、使い勝手やデバッグ時に発生したエラーについてのフィードバックもお待ちしております。

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。