JVMをネイティブコードレベルでデバッグ

先日、JVMをネイティブコードのレベルでデバッグしないといけなくなったので、このブログはそのメモです。例によって、僕はJVMチームの一員じゃありません。もし、よりよい方法をご存知の方は教えて下さい。


さて、これをやるはめになったのは、MavenをHudsonからある特定の方法で起動するとMavenがハングする、というバグが懸案になっていたからです。デバッグを長くやっていると、誰しもある種の勘というかそういうものが身についてきて、これは性質の悪い、下位レイヤからくるバグだというのが分かる時がありますが、これはそういうバグでした(本当はそんなアナログなものじゃなくて、脳内で症状に合致する仮説を組み立てようとした結果、妥当な仮説が存在しないというか、もっと検索的な感じなのですが)。


デバッガサポートをつけてJVMを起動するとバグは再現しないし、jconsoleをアタッチするとその途端に生き返り、Javaのレベルでのデッドロックは見つからない、といった具合なのです。Mavenのメインスレッドはgethostbyaddrかgethostbynameかそんなようなところでハングしているようなのですが、どうしてそういう事が起こりうるのか一向にわかりません。


そんなこんなでJavaのデバッガでは原因が究明できないので、いよいよネイティブコードのデバッガに乗り出すことにしました。

  • 最初に、Visual Studioが必要です。もしライセンスを購入していなければ、Express版は無償で利用でき、これで充分です。
  • デバッグシンボルサーバに接続してWindows APIをアドレスではなくシンボル名で見られるようにします。詳しくはこのMSDN記事を参照。なんでマイクロソフトはこれを出荷時の標準設定にしないのかわかりません。
  • JDKソースコードをダウンロードします。僕はSunに勤めているので過去のあらゆるビルドのJDKソースコードにアクセスできるのですが、公開されているソースコードはここしかみつかりませんでした。ソースコードは使っているバイナリにマッチする必要があるので、念のためここからソースとバイナリを1セットでダウンロードするのが安全でしょう。
  • デバッガをJVMにアタッチすると、Windows APIのシンボル名とJVM関連のシンボル名は自動で表示されるはずです。これは、JDKがシンボルファイル(jre/bin/*.pdb)とともに出荷されているからです。シンボルファイルには関数名などの情報の他に、そのシンボルがどのCソースファイルからコンパイルされたのかの情報が含まれています。
  • スレッドのコールスタックをうろうろしていると、Visual Studioはこの情報を使って、ソースファイルがどこにあるのかを尋ねるダイアログを表示してきます。恐らくJDKのオフィシャルビルドとは異なるディレクトリにソースファイルを展開したでしょうから、展開したソースツリーのどこにファイルがあるのかをVisual Studioに教えてあげて下さい。これで、ディスアセンブリだけじゃなくてCのソースが見られるようになります。やった!!


さて、ここまではよいのですが、僕はJITが生成したJavaコードを対応するJavaシンボルに結びつける方方がわかりませんでした。この結果、どのWin32スレッドがどのJavaコードを実行しているのかわからず、結局全ての情報を網羅的にネイティブデバッガから見ることができませんでした。誰か方法を知っていたら教えて下さい。


とはいえ、この情報が似たような境遇に居る人の手助けになることを期待しています。