liboctave のエラーハンドラを自分で定義して,デバッグを効率化

liboctave を使っていると,例えば要素数の異なる列ベクトルを足そうとしたときに

fatal: operator +: nonconformant arguments (op1 len: 2, op2 len: 5)

というエラーが発生し,プログラムが正常終了する.正常終了とは exit(1) による終了で,

  • コアファイル (core) がダンプされないのでバックトレースできない
  • このためどの関数が operator+ を呼び出してエラーを発生させたのかわからない

という問題があり,デバッグしにくい.そこで, liboctave のエラーハンドラを自分で定義して,エラーが発生したときの振舞いを自分で決められるようにしよう.
liboctave のエラーハンドラを設定するには, octave/lo-error.h で定義されている set_liboctave_error_handler という関数を使う.この関数の宣言は

typedef void (*liboctave_error_handler) (const char *, ...);
CRUFT_API extern void set_liboctave_error_handler (liboctave_error_handler f);

となっていて,自分で定義した関数を f に指定することで, liboctave のエラーハンドラを自分で定義できる.

デフォルトのエラーハンドラは(おそらく)libcruft/misc/lo-error.cで定義されている liboctave_fatal 関数のようで,この定義をソースから抜粋すると,

00048 static void
00049 verror (const char *name, const char *fmt, va_list args)
00050 {
00051   if (name)
00052     fprintf (stderr, "%s: ", name);
00053 
00054   vfprintf (stderr, fmt, args);
00055   fprintf (stderr, "\n");
00056   fflush (stderr);
00057 }

00086 void
00087 liboctave_fatal (const char *fmt, ...)
00088 {
00089   va_list args;
00090   va_start (args, fmt);
00091   verror ("fatal", fmt, args);
00092   va_end (args);
00093 
00094   exit (1);
00095 }

となっている. va_list, va_start, va_end は「可変引数リスト」を扱うための型と関数群 (cstdarg で定義されている), verror は lo-error.c で定義されている関数だ. liboctave_fatal を自分で書き直して, set_liboctave_error_handler で指定しよう.

具体的には,lexit: デバッグを効率化する終了関数で紹介した lexit 終了関数を使って,バックトレースが表示されるようにする:

#include <libhoge.h>  // lexit が定義されているヘッダ
#include <cstdlib>
#include <cstdarg>
#include <iostream>
#include <octave/config.h>
#include <octave/dColVector.h>
namespace some_namespace  // 適当な名前空間 (lexit と同じ)
{
static void verror (const char *name, const char *fmt, va_list args)
{
  if (name)
    fprintf (stderr, "%s: ", name);
  vfprintf (stderr, fmt, args);
  fprintf (stderr, "\n");
  fflush (stderr);
}
void liboctave_error (const char *fmt, ...)
{
  va_list args;
  va_start (args, fmt);
  verror ("liboctave fatal", fmt, args);
  va_end (args);
  lexit(btfail);  // バックトレースを出力し,終了
}
}; // end of some_namespace

テストしてみよう:

using namespace std;
using namespace some_namespace;
int main(int argc, char**argv)
{
  set_liboctave_error_handler (&liboctave_error);
  ColumnVector x(2,1.0), y(0);
  cout<<(x+y)<<endl;
  return 0;
}

このプログラムでは x と y の要素数が異なるから, x+y の演算に失敗する.このときプログラムは,

liboctave fatal: operator +: nonconformant arguments (op1 len: 2, op2 len: 0)
------
the program is terminated
  at octave-errorhandler.cpp:36:
  within the function: void some_namespace::liboctave_error(const char*, ...)
backtrace:
./a.out(_ZN12some_namespace7_exitlv7__lexitENS0_10TExitLevelEiPKcS3_+0x611)[0x804aec1]
./a.out(_ZN12some_namespace7_exitlv6_lexitENS0_10TExitLevelEiPKcS3_+0x36)[0x804a694]
./a.out(_ZN12some_namespace15liboctave_errorEPKcz+0x4a)[0x8049fdd]
/usr/lib/octave-3.0.1/liboctave.so(_Z19gripe_nonconformantPKcii+0x38)[0xb7afb0b8]
/usr/lib/octave-3.0.1/liboctave.so(_ZplIdE6MArrayIT_ERKS2_S4_+0x116)[0xb7a86766]
./a.out(_ZplRK12ColumnVectorS1_+0x26)[0x804a634]
./a.out(main+0x64)[0x8049e64]
/lib/i686/cmov/libc.so.6(__libc_start_main+0xe5)[0xb74c6455]
./a.out(_ZNSt8ios_base4InitD1Ev+0x3d)[0x8049d11]

というエラーを出力する.バックトレースを調べれば,どの関数でエラーが発生したのかが分かり,デバッグがしやすくなるだろう.もちろん, lexit(btfail) の代わりに lexit(abort) を使ってコアファイルをダンプさせることで,より深いデバッグも可能だ.