デバッガ:デバッグAPIの実例

デバッガ:デバッグAPIの実例

ここでは既存の環境や言語におけるデバッグAPIの実例について紹介します。


SquirrelのデバッグAPI

SquirrelとはLuaから派生したC++やJavascriptなどに似たLL言語です。 スクエニのとあるゲームで使われています。

スタック言語なので、少し分かりずらいところがあるかもしれません。 HSQUIRRELVM は ScmVM のようなものです。とりあえず、APIの説明をリファレンスの日本語訳から引用します。

Squirrel リファレンス日本語訳

 Squirrel VMは非常に単純なデバッグインタフェースを用意しており、
 これによって完全機能のデバッガを構築することが用意である。
 sq_setdebughookによって、VMの 1行ごとの実行時や関数の呼び出しや終了時に
 呼ばれるようなコールバック関数を設定することが可能である。
 このコールバックは現在の行、ソース、(あるなら)関数名を引数として持つ。
 void sq_setdebughook(HSQUIRRELVM v);
 
 次のコードはデバッグフックがどのようなものかを示す
 (明らかにこの関数をC言語で実装することは可能である)。
 
 function debughook(event_type, sourcefile, line, funcname) {
    local fname = funcname? funcname : "unknown";
    local srcfile = sourcefile? sourcefile : "unknown"
    switch (event_type) {
    case 'l': // called every line(that contains some code)
        ::print("LINE line [" + line + "] func [" + fname + "]");
        ::print("file [" + srcfile + "]\n");
        break;
    case 'c': // called when a function has been called
        ::print("LINE line [" + line + "] func [" + fname + "]");
        ::print("file [" + srcfile + "]\n");
        break;
    case 'r': // called when a function returns
        ::print("LINE line [" + line + "] func [" + fname + "]");
        ::print("file [" + srcfile + "]\n");
        break;
    }
 }
 
 引数event_typeは次のいずれかになる。
 イベント型: 説明
 'l': 各行の実行時
 'c': 関数の呼び出し時
 'r': 関数の終了時
 
 完全機能のデバッガは常にローカル変数と呼び出しスタックを表示することができる。
 呼び出しスタック情報はsq_getstackinfos()によって検索することができる。
 SQInteger sq_stackinfos(HSQUIRRELVM v, SQUnsignedInteger level, SQStackInfos *si);
 
 ローカル変数情報はsq_getlocal()によって検索することができる。
 const SQChar * sq_getlocal(HSQUIRRELVM v, SQUnsignedInteger level, SQUnsignedInteger nseq);
 
 行ごとのコールバックを受けるには、スクリプトをデバッグ情報付きでコンパイルする必要がある。
 これはsq_enabledebuginfo()を使うことで可能になる。
 void sq_enabledebuginfo(HSQUIRRELVM v, SQBool enable);

ちなみに、他の環境のデバッグAPIも見ると分かるのですが「完全機能のデバッガ」というのはこれだけでは実現できません。何を「完全」と呼ぶかにもよるのですが、最低でも例外発生時のコールバックは欲しいです。

Squirrelはスタック言語なために、一部の引数や戻り値はスタックに詰まれることが前提となっています。 これを分かりやすく修正すると(というかでっち上げ)、以下のようになります。

API一覧

* sq_setdebughook

 void sq_setdebughook(HSQUIRRELVM v,
                      SQDebugHook debughook);

* sq_stackinfos

 ErrorCode sq_stackinfos(HSQUIRRELVM v,
                         SQUnsignedInteger level,
                         SQStackInfos *si);

* sq_getlocal

 const SQChar *sq_getlocal(HSQUIRRELVM v,
                           SQUnsignedInteger level,
                           SQUnsignedInteger index,
                           HSQOBJECT *value);

* sq_enabledebuginfo

 void sq_enabledebuginfo(HSQUIRRELVM v,
                         SQBool enable);

* SQStackInfos

 struct SQStackInfos {
     SQChar* funcname; //< 関数の名前
     SQChar* source; //< ファイルや直接実行された文字列の一意なキー
     SQInteger line; //< 行番号
 }

LuaのデバッグAPI

Squirrelの生みの親とも言えるスタックベースの言語です。こちらの方がSquirrelよりデバッグAPIが複雑なので後回しました。 全体的にSquirrelはLuaのデバッグAPIのいいとこどりというか、複雑な割に使うことが少ない機能をばっさりカットしたような感じになっています。

Luaの場合は、C言語用ではなくLua言語から使えるAPIを紹介してきます。 詳しくは以下のページを参考にしてください。
Lua5.1 リファレンスマニュアル日本語訳

API一覧

* debug.sethook

 debug.sethook(hook, mask, count);

* debug.getinfo

 debug.getinfo(level, what)

* debug.getlocal

 debug.getlocal(level, local)

* lua_Debug

 struct lua_Debug {
     // コールバックが呼ばれたときにのみ使われ、LUA_MASKRETとか
     // LUA_MASKCALLとかの値が入っています
     int event;
 
     // 関数に関する適当な名前で、一番よさそうなものを探してきます
     const char *name;           /* (n) */
 
     // name フィールドの説明
     // どのように関数が呼ばれたかによって、以下のいずれかになります。
     // "global", "local", "method", "field", "upvalue", or "" (空文字列。
     // 他に適当なものがなければこれ)
     const char *namewhat;       /* (n) */
 
     // 関数の種類。Cの関数なら"C"、Luaの関数なら"Lua"、メインモジュールなら"main"
     const char *what;           /* (S) */
 
     // *ソースファイルやevalで実行される文字列を一意に特定する何か*
     // 関数がファイルで定義されたら"@ファイル名"、文字列から実行された場合はその文字列
     const char *source;         /* (S) */
 
     // 現在実行中の行番号
     int currentline;            /* (l) */
 
     // その関数の上位値の数。上位値とはクロージャにバインドされた変数です。
     int nups;                   /* (u) 上位値の数 */
 
     // 関数定義の開始位置の行番号。
     // この情報が利用可能でなければ、currentline は -1 に設定されます
     int linedefined;            /* (S) */
 
     // 関数定義の終了位置の行番号。
     int lastlinedefined;        /* (S) */
 
     // エラーメッセージに使う「可読」バージョンの source。
     char short_src[LUA_IDSIZE]; /* (S) */
 
     /* private part */
     ...
 };

サンプル実装

 使い方
 lua debugger.lua inputfile.lua

詳しい使い方はデバッグ開始後にh(help)と打つと表示されます。

RubyのデバッグAPI

多分このページを読む人でこれを知らない人はいないと思うのですが、一応説明しておきますと、まつもとゆきひろ(通称Matz)が設計している国産のスクリプト言語です。世界中で使われています。

毎度のように、下の説明はほぼ以下のリファレンスからコピペしてきたものです。デバッガに必要なAPIは限られているので、適時抜き出しています。
オブジェクト指向スクリプト言語 Ruby リファレンスマニュアル

API一覧

* set_trace_func

 set_trace_func(trace_proc)

* caller

 caller([level])

例:

 def foo
   p caller(0)
   p caller(1)
   p caller(2)
   p caller(3)
 end
 
 def bar
   foo
 end
 
 bar
 
 => ["-:2:in `foo'", "-:9:in `bar'", "-:12"]
    ["-:9:in `bar'", "-:12"]
    ["-:12"]
    []

* local_variables

 local_variables

* trace_var

 trace_var(varname, hook)
 trace_var(varname) {|newval| .... }

例:

 trace_var(:$v) {|val| puts "$v=#{val.inspect}" }
 $v = "foo"   #=> $v="foo"
 $v = 1       #=> $v=1

* eval

 eval(expr, [binding, [fname, [lineno=1]]])

サンプル実装

 使い方
 ruby debugger_ruby.rb [debuggee.rb]

詳しい使い方などはデバッグ開始直後に表示されます。


Last modified : 2015/06/21 10:00:40 UTC