LoginSignup
14

More than 5 years have passed since last update.

Cでmrubyコード実行時に発生したエラーの内容とバックトレースを取得

Posted at

mrubyのコードをCで実行すると、エラーが発生することがありますが、mrb_load_stringなどで普通に実行しても、エラー内容やバックトレースは表示されません。
そこで、Cでエラー内容とバックトレースを取得する方法を調べてみました。

どんなエラーが起こったのか表示されない!!

mrb_load_string(mrb,
                "def foo\n"
                "  xxx(0)\n"
                "end\n"
                "foo");

欲しい情報

エラー内容
foo.rb:1: undefined method 'xxx' for main (NoMethodError)
バックトレース
foo.rb:2:in Object.foo
foo.rb:4

方法

文字列を実行して、エラーとバックトレースを取得するコードはこのようになりました。

コード例

#include <stdio.h>
#include <mruby.h>
#include <mruby/compile.h>
#include <mruby/string.h>
// extern "C" (C++の場合必要)
mrb_value mrb_get_backtrace(mrb_state *mrb, mrb_value self);

...

mrb_state *mrb = mrb_open();

// コンテキストを生成、ファイル名を設定
mrbc_context *context = mrbc_context_new(mrb);
mrbc_filename(mrb, context, "foo.rb");

// 一時オブジェクトがつくられるのでGC arenaを保存
int ai = mrb_gc_arena_save(mrb);

mrb_load_string_cxt(mrb,
                    "def foo\n"
                    "  xxx(0)\n"
                    "end\n"
                    "foo",
                    context);

mrb_value exc = mrb_obj_value(mrb->exc);

// エラー内容を取得
mrb_value backtrace = mrb_get_backtrace(mrb, exc);
puts(mrb_str_to_cstr(mrb, mrb_inspect(mrb, backtrace)));

// バックトレースを取得
mrb_value inspect = mrb_inspect(mrb, exc);
puts(mrb_str_to_cstr(mrb, inspect));

// 例外をクリア
mrb->exc = 0;

mrb_gc_arena_restore(mrb, ai);

mrbc_context_free(mrb, context);
mrb_close(mrb);

出力
["foo.rb:2:in Object.foo", "foo.rb:4"]
foo.rb:1: undefined method 'xxx' for main (NoMethodError)

ポイント

コンテキストを用意する

文字列をロードする際は、mrb_load_string_cxt関数を使って、コンテキストを同時に渡す必要があります。
ファイル名は、mrbc_filename関数でコンテキストに設定します。

コンテキストを渡さないと、バックトレースが取得できません。

例外オブジェクト

例外オブジェクトはmrb->excで参照できます。

inspectでエラー内容を取得

例外オブジェクトをinspectすることで例のようなエラー文字列が取得できます。

バックトレースの取得

mrb_get_backtraceが、例外オブジェクトからバックトレース(StringのArray)を取得する関数です。
ヘッダにはないので自分でプロトタイプ宣言を用意する必要があるようです。

バックトレースは例外発生後すぐに取得する必要がある

バックトレースを取得する前にinspectを行う処理を書いたところ、
バックトレースが取得できなくなることに気がついたので、issueを送ってみました

Matz氏からすぐにご返事をいただき

  • mrb_get_backtraceはmrb_stateからバックトレースを取得するため、他の処理を行ってしまうと
    それが上書きされてしまう

  • したがって、バックトレースは例外発生後すぐに取得する必要がある

とのことでした。

例外はクリアする

mrb->excに0を設定することによって、例外をクリアする必要があります。
mrb->excが残っていると、次にスクリプトを実行するときに失敗してしまいます。

GC arena の save / restore

例外オブジェクト、処理中にCから生成したオブジェクトは、そのままでは GC arena に登録されて GC から保護されてしまい、いつまでも回収されません。

そこで、GC arena を save / restore して、GC の対象にさせる必要があります。

参考になったページ

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14