ランタイムとDLL境界

Rubyを経由してgdbm.dllをデバッグした当初にちょっとはまったことがあった。

Rubyの拡張ライブラリィである、dbm.soとgdbm.soは両方ともgdbm.dllの実装を利用するために動的にリンクするように作られているのだが、Visual Studioを使用してデバッグ用のgdbm.dllを作った場合にdbm.soを経由してデバッグした場合は問題無く動作するのに、gdbm.soを経由するとgdbm.dllでSegmentation Fault が発生して、Rubyごと落ちてしまう現象が発生したのである。


あれれと思い、通常通りnmakeでビルドしたgdbm.dllを使うとSegmentation Faultは消失する。最初は理由が全く分からなかった(最近こんなのばかりだ)し、深い深いデバッグが必要なのかと頭を抱えたのだが、問題は以外と単純だった

Visual Studioデバッグ用のビルドを行った訳だが、そこで出来たgdbm.dllをDependency Walker(ここ最近一番起動回数が多いWin32アプリかも..)で見てみよう。

ここで重要なのはCランタイムにMSVCR80.DLLではなく、MSVCR80D.DLLが参照されていることだ。そう、デバッグ用としてVisualStudioでコンパイルされたDLLはそのランタイムにもデバッグ用が使われるのである。

一方拡張ライブラリィであるgdbm.soはextconf.rbにより作成されたMakeFileによりビルドされるが、このときのコンパイラに与えられるランタイム-コンパイルオプションをextconf.rbから作成されたMakeFileから抽出してみよう。

cl -nologo -I. -I. -Ie:/ruby/lib/ruby/1.8/i386-mswin32_80 -I. -MD  -O2b2xty- -DHAVE_GDBM_H -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE -c -Tcgdbm.c

重要なのは"-MD"つまりMSVCRT.libを使用してマルチスレッド用のDLLを作成する、つまりデバッグ用では無いということだ。
結果として依存しているランタイムがデバッグ用と非・デバッグ用と一致しないことになる。これがSegmentation Fault の原因だろう。

それを証明するために、gdbm.soをデバッグ用のランタイムにリンクするようにMakeFileを修正してビルドしてみた。

CFLAGS   =  -MDd   -O2b2xty-  

マルチスレッド-デバッグ用のランタイムを使用場合は、"-MDd"を使用すれば良い。
以下、ビルドの結果出来たgdbm.soの依存関係だ。

デバッグ用のランタイム(MSVCR80D.DLL)が参照されていることが分かる。
この拡張ライブラリィを使うことで、gdbm.soとgdbm.dllのデバッグ時にSegmentation Faultは発生しなくなった。

参考 : DLL の境界を越えて CRT オブジェクトを渡す場合に発生する可能性のあるエラー


結局、MSVCR80版のRubyパッケージを作る発露となった時と同じ問題が発生していたのである。

追記 : Rubyのランタイムも同様にデバッグ時に問題が発生する場合が考えられる。その場合上記同様にビルド時にデバッグ用ランタイムを使用する設定を行う必要がある。