pdb2mdb で、Mono 環境でもスタックトレースに行番号が出るようにする

要約

.net framework 上で Debug ビルドすることで得られる .pdb ファイルを、Mono 2.x で使える .mdb ファイルに変換することで、.net でビルドしたバイナリのデバッグ情報を Mono 環境でも使えるようにする、という話。

こうすることで、.net framework 上でビルドしたバイナリを Mono 環境で動かした時にも、行番号やファイル名と言ったデバッグ情報を利用できるようになる。

.pdb*1 ファイル と .mdb*2 ファイル とは

.net framework に含まれる C# コンパイラである csc.exe など(VisualStudio でのコンパイルも然り)や、mono の C# コンパイラである gmcs は、デバッグ情報を生成する機能がある。

それらコンパイラが生成するデバッグ情報のファイルが、

  • .net framework なら .pdb ファイル
    • csc.exe なら、/debug オプションを付けると生成してくれる
  • mono なら .mdb ファイル
    • gmcs なら、-debug オプションを付けると生成してくれる

である。

このデバッグ情報ファイルは、デバッグ対象となるアセンブリの中のどの位置の IL が、どのソースコードのどこに対応するのか、という情報を持っている。

例えば、例外の発生したときのスタックトレースに出てくるファイル名と行数は、このデバッグ情報ファイルを使って、IL の位置からファイル名と行を逆算することによって導出されているのである。

なので、デバッグ情報ファイルが無ければ、例外などのスタックトレースにはファイル名や行といった情報が出てこなくなり、デバッグがかなり不便な状況になる。

しかしながらも、.net で用いられる .pdb ファイルと、mono で用いられる .mdb ファイルには互換性がない。ので、せっかく片方でビルドしたバイナリがもう片方で動くという相互運用性があるにも関わらず、デバッグ情報に限って相互運用性がないという状況になってしまうのである。

pdb2mdb を使う

ここで、.pdb ファイルを変換して .mdb ファイルを生成する pdb2mdb というツールがある。

なお、上のバイナリやソースコードにまつわる紆余屈折は後述する。

例えば HogeHoge.exe と HogeHoge.pdb があるなら、.net framework のインストールされた Windows 上で

pdb2mdb.exe HogeHoge.exe

のように pdb2mdb を実行することで、HogeHoge.mdb が得られる。

あとは、HogeHoge.exe と一緒に HogeHoge.mdb を Mono 環境に持って行った上で、

mono --debug HogeHoge.exe

のように実行することで、Mono 環境でも行番号やファイル名の付いたスタックトレースを見ることが出来るはずである。

注意点としては、

  • pdb2mdb.exe は Windows 上で実行する必要がある。
    • pdb2mdb が、.pdb ファイルの読み取りのために COM 経由で提供される .pdb リーダーを使っているため。
  • pdb2mdb の引数には、アセンブリ(.exe や .dll)を指定する必要がある。
    • .pdb ファイルではない。
  • mono 環境で実行する場合、--debug オプションを渡さないと、デバッグ情報は使われない
    • そうでないと、デバッグのための(一部の) JIT 最適化の無効化が行われないそうだ。

という点が挙げられる。

pdb2mdb 紆余屈折

(以下は、pdb2mdb に関する四方山話である。)

先述の .pdb と .mdb の相互運用性のなさをどうにかするために調べたところ、Robert Jordan 氏が Mono のメーリングリストで公開してくれている、pdb2mdb というツールがあると分かった。これは、.net の .pdb ファイルを読み取り、.mdb ファイルを書き出すツールである。

しかしながらも、この Robert Jordan 氏のバージョンがはき出す .mdb を Mono 2.2 環境で実際に使ってみたところ、はき出された .mdb のバージョンが古くて、最近の Mono (2.x) では使えないという問題があるようである。

これについて調べてみると、http://n2.nabble.com/pdb2mdb-td2271321.html に Robert Jordan 氏が pdb2mdb の新しいバージョン(09/02/05)のソースコードを流してくれており、これを用いると、Mono 2.2 や 2.4*3 で使える .mdb をはき出せるらしい。

だが、09/02/05 のバージョンのソースコードは、Mono.CompilerServices.SymbolWriter.dll の 2.0.0 だとビルドが通らず、Mono.CompilerServices.SymbolWriter.dll の 2.0.5 を使ってもビルドが通らない。

オリジナルのメールの文面やソースには、pdb2mdb の 09/02/05 バージョンがどのバージョンの SymbolWriter.dll とリンクすることを意図しているのか明示されていなかった。が、

  • SymbolWriter.dll 2.0.0 とリンクしようとすると、インターフェースなどのシグニチャが片っ端から合わない
  • SymbolWriter.dll 2.0.5 とリンクするのなら、MonoSymbolWriter.OpenMethod() メソッドが internal になってしまっていることによるコンパイルエラーだけ

であるという状況から、SymbolWriter.dll の 2.0.5 とリンクすることが意図されているのだと推測した。

そこで、internal メソッドである MonoSymbolWriter.OpenMethod() を Reflection によって強引に呼び出して、とりあえず動くように workaround することで、pdb2mdb の 09/02/05 バージョンを何とかビルドする事が出来た。

こうして作った pdb2mdb の 09/02/05 バージョンに .pdb ファイルを通して .mdb ファイルを生成させてみると、確かに Mono 2.2 で読み込みむ事のできる .mdb ファイルが得られたのである。

ちなみに、筆者の手元の Mono には Mono.CompilerServices.SymbolWriter.dll の

  • 1.0.5000.0
  • 2.0.0
  • 2.0.5

しかないためにこのような試し方になっているが、本当はもっと適切なバージョンがあるのかもしれない。

また、言うまでもないが、MonoSymbolWriter の internal (に変更された) メソッドを強引に呼び出しているため、とりあえず動いているが何らかの問題が出る可能性は十分にある。

*1:Program Debug Database

*2:Mono DeBugger

*3:09/02/05 時点の SVN HEAD