Next: , Previous: , Up: GMP Basics   [Index]


3.12 デバッグ

スタックオーバーフロー

システムによりますが,セグメンテーション違反(segmentation violation)やバスエラー(bus error)が出ると,スタックオーバーフローが発生していることを意味します。この発生個所を特定するには,Build Optionsの‘--enable-alloca’を指定して下さい。

GCCの比較的新しいバージョンでは, ‘-fstack-check’オプションが利用でき,これでシステムがダメージを受ける前に,スタックオーバーフローを感知できるようになります。また,‘-fstack-limit-symbol’ や ‘-fstack-limit-register’を使うと,システム側では何もしなくてもチェックできるようになります(see Options for Code Generation in Using the GNU Compiler Collection (GCC))。 これらのオプションはGMPのビルド時に‘CFLAGS’フラグに指定しておく必要があります(see Build Options)。アプリケーション側にはこれらのオプション指定の影響はない筈ですが,関数呼び出しやスタック確保時にオーバーヘッドが多少加わって遅くなるかもしれません。

ヒープ領域の問題

GMPを使ってアプリーションを作ると,問題の多くがヒープ領域の破壊に起因することが分かります。 GMPの変数の初期化に失敗すると,予測不能の影響が出てGMP の実行そのものにも及んできたりします。 GMP変数を2回以上初期化したり,変数の解放に失敗したりすると,メモリリークの原因になります。

このような場合は,mallocデバッガーの利用をお勧めします。 GNUやBSDシステムには 標準Cライブラリの malloc関数に診断機能が付いおり,詳細はAllocation Debugging in The GNU C Library Reference Manual, もしくは ‘man 3 malloc’を参照して下さい。他にも,順不同に示すと下記のようなツールがあります(訳注:アクセス不可のURL多し)。

GMPのデフォルトのメモリ割り当てルーチンはmemory.cにあり, ここには簡単な防人スキーム が組み込まれていて,#define DEBUGをこのファイルで有効にすると使えるようになります。 基本的にはGMP開発時にバッファオーバーランを検知するための機構ですが,他の目的にも役に立つでしょう。

スタックのバックトレース

ある種のシステムでは,GMPがデフォルトで使用するコンパイラオプションがデバッグの邪魔をする可能性があります。 特にx86系や68k系では, ‘-fomit-frame-pointer’オプションが使えますが,これを指定するとスタックのバックトレースが妨げられてしまいます。メモリの問題を無視したり,コンパイラのバグを隠してしまう可能性はありますが,デバッグ時には,このオプションを付けずに再コンパイルしておいた方がいいでしょう。

GDB, GNUデバッガ

サンプルとして.gdbinitがGMPのTARボールに同梱されており,これでドキュメント化されていないダンプ関数を呼び出してGMP変数の中身をGDB で表示させる方法が分かるようになっています。これらの関数はアプリケーションの最終実行コードには含まれていません。ドキュメント化していないので,将来のGMPでは互換性のない形で変更される可能性があります。

ソースプログラムへのパス

GMPは同名のファイルを多数持っており,それぞれ別のディレクトリに入っています。mpzmpz, mpfを例に挙げると,これらのディレクトリにはinit.cがそれぞれ含まれています。デバッガはどれを指しているのか決められませんので,それぞれのCファイルへの絶対パスを付加してビルドしておくと,どのinit.cか分かるようになります。やり方としては,オブジェクトファイルを生成するディレクトリを別に作り,そこからソースがあるディレクトリへの絶対パスを下記のように指定します。

cd /my/build/dir
/my/source/dir/gmp-6.1.2/configure

これはVPATH経由でも実行できますが,GNU makeが必要となります。他にも,.c.loの生成規則を変更することでできるようになります。

アサーションによるチェック

--enable-assertというビルド時のオプションを付加することで,ライブラリの整合性チェックが使えるようになります(Build Options参照)。大方のアプリケーションに対して,値の制限ができるようになります。アサーションエラーが発生すると,ライブラリかコンパイラのバグのように,メモリ破壊として検知されるようになります。

基盤的 mpn関数を使ったアプリケーションの場合,--enable-assertを使うと,関数の引数のチェックを行うことができ,大変便利です。こういうアプリケーションは微妙な制限を付ける必要があるからです。しかしながら,このアサーションチェックは汎用Cコードだけで利用でき,アセンブラコードでは使えませんので,最大限活用したいのであれば,--disable-assemblyオプションをつけてビルドして下さい。

一時メモリ領域のチェック

ビルド時のオプション --enable-alloca=debugを使うと,GMPの一時メモリ領域の各ブロックが,別々のmalloc (もしくは mp_set_memory_functionsで指定された関数)を使って確保されるようになります。

これによって,mallocデバッガが,内部エリアの外側からのアクセスや,解放されていないメモリを検知できるようになります。通常のビルドを行うと,GMPの一時メモリ領域のブロックは,その利用範囲内に限定されたバラバラのものとして確保されたり,コンパイラにビルドインされているalloca関数で確保されるので,mallocデバッガのフックが効かなくなってしまいます。

デバッグしやすくするために

今まで縷々説明してきたことを要約すると,最大限デバッグしやすくしようとするならば,GMPのビルド時のオプション指定は次のように行って下さい。

./configure --disable-shared --enable-assert \
  --enable-alloca=debug --disable-assembly CFLAGS=-g

C++の場合は‘--enable-cxx CXXFLAGS=-g’もお忘れなく。

チェッカー

GCCチェッカー(https://savannah.nongnu.org/projects/checker/)はGMP で利用可能です。 これはスタブライブラリを持っており,GMPのアプリケーションにこのチェッカーを付けてコンパイルすると,通常のGMPビルドも使用できるようにしてくれます。

GMPの内部にチェッカーを付加してGMPビルド行うことも可能ですが,結果としてものすごく低速になってしまいます。GNU/Linux上では次のようにビルド時の設定を行います。

./configure --disable-assembly CC=checkergcc

GMPのアセンブラコードにはチェック機構が入っていませんので,--disable-assemblyでビルドしなくてはなりません。 現在のチェッカーのバージョン(0.9.9.1)では標準C++ライブラリをサポートしていませんので,GMP C++でもチェック機構は利用できません。

Valgrind

Valgrind (http://valgrind.org/)はx86, ARM, MIPS, PowerPC, S/390用のメモリチェッカーです。 各マシン用の命令を変換してエミュレートし,未初期化データ(ビット単位のレベルまで)や,不正ポインタやメモリリークが発生しているメモリアクセスに対して強力なチェックを行います。

Valgrindは全ての命令をサポートしている訳ではなく,特に,最近ISAに追加された命令はサポートしていません。 従って,Valgrindのエミュレーションは最新のGMP,もしくは最新のGCCでコンパイルされたGMPに対しては有効でない可能性があります。

GMPのアセンブラコードは読み込むリムが大きくなっても効率良く実行できるようにするためのものです。GMPは,リム配列の最初から最後まで読み込みを行い,自然なアラインメントを行って大きなデータタイプにも対応することになります。つまり,あらかじめ確保されたメモリエリアの外側を読み込むこともあり得るので,この際にはValgrindが警告を発することになります。ウザいようならValgrindの‘--partial-loads-ok=yes’オプションを使って下さい。

その他の問題

GMPそのものに潜むバグは,アプリケーション側の問題ではないということを確認する必要があります。Reporting Bugsを参照して下さい。