たどり着いた経緯
C/C++ 言語で開発しているとバッファオーバーランを見つけたい、というか見つけないと開発プロジェクトが破たんしかねない事態に追い込まれることがあります。
Linux だと Valgrind があるのですが、私が働く会社ではゼロ年代からの慣習で初期開発は Windows で行うこともよくあり、いい(役立ち、そして会社に購入申請しなくていい)ツールがないのが悩みです。
そんななか DUMA library というのを見つけました。そのフロントページにはこう書いてあります。
runs on Linux / U*ix and MS Windows NT/2K/XP operating systems
これはいい。さっそく使おう。
……さくっと動けばよかったのですが、私の技術力では解決できない課題が残ってしまいました。ここでは、完成した情報の提供はあきらめ、七転八倒した私的経験を記します。
なお、ここでは 2014-08-27 現在最新の DUMA library 2.5.15 をベースにします。
マニュアルにあるインストール手順
パッケージの INSTALL ファイルには次のようにあります。
II. Windows 2000/XP, Visual Studio Environments:
Compile and run 'createconf', that generates the file duma_config.h.
Compile 'dumalib' and set the directories 'INCLUDE' and 'LIBS'
in your environment or project.
つまり
- ソリューションファイルを開く
- createconf をビルドして実行
- dumalib をビルド
- 自分のプロジェクトでヘッダファイル(名前は書かれていませんが duma.h です)をインクルードし dumalib.lib をリンクする
という手順です。ソリューションファイルは複数ありますが、私はプロジェクト都合で VS 2005 のファイルを VS 2013 で開きました。
でも、これだけじゃ動かなかったんですね。
ランタイムライブラリを自分のプロジェクトに合わせました
まず dumalib.lib がリンクしているランタイムライブラリが私が担当するプロジェクトと衝突しました。
DUMA library パッケージでは標準ではマルチスレッド デバッグ(/MTd)とリンクする設定になっています。
たいていのプロジェクトではマルチスレッド デバッグ DLL(/MDd)とリンクしていますよね。リンクするライブラリが違うモジュールが混在するので衝突するのです。
DUMA library のプロジェクトの設定を変更し、ランタイムライブラリを自分のプロジェクトと同じものに変更しましょう。VS 2013 だとプロパティページの左側リストを [構成プロパティ]→[C/C++]→[コード生成]とたどり、右側設定項目[ランタイム ライブラリ]を変更します。
システムの malloc/free/operator new/operator delete の置き換えはあきらめました
次に dumalib.lib が持つ malloc/free/operator new/operator delete が VS のランタイムライブラリとバッティングしました。
DUMA library はシステムのメモリ操作関数を置き換えることも意図しています。しかし同じシンボルが複数あるとビルドに失敗します。私の技術力では置き換えられません。
ここで VS のランタイムライブラリを無視してはいけません。DUMA library が置き換えるのは malloc/free/operator new/operator delete だけですから、ランタイムライブラリの他の関数が使えなくなります。
システムの malloc/free/operator new/operator delete の置き換えはあきらめて、dumalib を再ビルドします。
コンフィギュレーションは createconf が握っています。createconf からビルドし直しです。createconf のプロジェクトに外部から次の3つのマクロ定義を与えて再ビルド、そののちに dumalib.lib をビルドします。VS 2013 だとプロパティページの左側リストを [構成プロパティ]→[C/C++]→[プリプロセッサ]とたどり、右側設定項目[プリプロセッサの定義]を編集します。
- DUMA_NO_GLOBAL_MALLOC_FREE
- DUMA_NO_CPP_SUPPORT
- DUMA_SO_NO_CPP_SUPPORT
これで DUMA library が監視できるのは duma.h により malloc/free をリネームしたソースコードのみとなります。
今どきのデスクトップアプリなら C++ で開発するでしょうから、これは大変な制限です。
私のプロジェクトではデバッグしたいのは C 言語で開発されたエンジン(アプリはエミュレータ)でしたから、首の皮一枚で助かりました。
ソースコードを1行コメントアウトしました
さらに dumalib.lib が __environ という外部変数を参照していてリンクに失敗します。この参照は duma.c にあります。
extern char **environ;
こが VS 2013 と相性が悪いです。VS 2013 の stdlib.h では environ が _environ のマクロとして定義されているので、duma.c の参照は _environ に置換され、C 言語の名前規則により __environ (アンダースコアが2つ)の参照となってランタイムライブラリに無い変数を参照してしまうのです。
この1行はコメントアウトします。duma.c は stdlib.h をインクルードしていますから、きちんとランタイムライブラリの environ を参照します。
stacktrace.lib もリンクしました
すると dumalib.lib が解決できない外部参照を抱えています。
DUMA library の VS のプロジェクトの依存関係を見ると、dumalib プロジェクトは stacktrace プロジェクトに依存しているのです。
stacktrace.lib も忘れずにリンクしましょう。
怖くて製品には同梱できません
DUMA library は GPL フリーソフトウェアです。
ないと思いますが、プロプライエタリソフトウェアの製品に同梱しないように。
使ってみると
こうして DUMA library をリンクしたプロジェクトのデバッグ方法は README ファイルにあります。
If your program has one of the errors detected by DUMA, it will get a segmentation fault (SIGSEGV) at the offending instruction. Use the debugger to locate the erroneous statement, and repair it.
エラーを発見すると、私たちのプログラムが落ちます。
初めて落ちたときは、訳が分かりませんでした。落ちた箇所はソースコード上に目立った瑕疵はなく、変数の値も普通です。なぜ落ちるのか思い当たる節がありません。そもそもクラッシュする可能性はメモリエラーには限らないわけです。そこから「バッファオーバーランしたんだ」などと気づくには相当に頭を働かせる必要があります。
しかし気づいた後の解決は速いです。エラー発生の瞬間に落ちますから、エラーの経緯が全て残っています。分からなければ再度走らせればいいんです。通常のメモリ破壊とは異なり、いつも同じ場所(つまり破壊の原因)で落ちますから。
まとめ
Windows で(無料で)使えるメモリエラー検査ツールがあったと喜びましたが、私の技術力では制限が残ってしまいました。
しかし制限内ならとても有用なツールです。使ってよかったと思います。
ここで解決できなかった制限を回避する方法を見つけた方は、きっとネットに公開していただけると信じて結びとします。