デバッガの実装(Ruby/Python編)

最後に、Ruby,Pythonのデバッガの実装について調べてみました。

Pythonには、pdbRubyにはdebug.rbというデバッガが存在します。
これらは、各言語が提供するフック関数を利用して実装されています。フック関数を設定する関数(メソッド)は以下になります。

  • Python
    • sys.settrace (Cレベルでは、ceval.cのPy_tracefunc関数*1 )
  • Ruby
    • Kernel#set_trace_func (Cレベルでは、eval.cのset_trace_func関数)

Pythonのsys.settraceの引数で渡した関数は、以下のタイミングで呼び出されます*2

  • call: なんらかの関数呼び出し時
  • line: Pythonインタプリタが新しい行を実行する時
  • return: 関数の呼び出しからreturnする寸前
  • exception: 例外が発生した時
  • c_call: (拡張ライブラリ等の)C関数が呼び出されるとき
  • c_return: C関数から返ってきたとき
  • c_exception: C関数内部でPythonの例外が発生した時

また、RubyのKernel#set_trace_funcの引数で渡した関数は、以下のタイミングで呼び出されます*3

  • "line" ... 式の評価。
  • "call" ... メソッドの呼び出し。
  • "return" ... メソッド呼び出しからのリターン。
  • "c-call" ... Cで記述されたメソッドの呼び出し。
  • "c-return" ... Cで記述されたメソッド呼び出しからのリターン。
  • "class" ... クラス定義、特異クラス定義、モジュール定義への突入。
  • "end" ... クラス定義、特異クラス定義、モジュール定義の終了。
  • "raise" ... 例外の発生。

フック関数が呼び出されるときに、そのときの文脈(どのようなローカル変数が存在するか、次に行われる処理では、どのクラスの何のメソッドを呼ぼうとしているのか等)が引数として渡されます。フック関数はそれを記録、処理していくことで、breakpoint, バックトレース等が実現できます。


フック関数方式のデバッガは、以下のような利点があります。

  • デバッガ実装のために、言語実装者側が提供するべき機能の実装負担が少ない(フック関数に関する実装のみ)
  • デバッガ実装者側は、その言語だけでデバッガ実装を行うことができる

しかし、さきほど上で述べたすべてのタイミングでフック関数が呼び出される(そして、フック関数に渡す引数の用意も毎回行われる)ため、デバッグ時はプログラムの動作がかなり遅くなります。
特にRubyでrequire等ライブラリのロードを行うと、require内部でもフック関数が呼び出されるためにとても遅いです。

動作の遅さを解消するために、Rubyのdebug.rbと違って、拡張ライブラリも用いて(C言語も用いて)デバッガを実装することによって、フック関数*4をなるべく用いない高速なデバッガを作った人もいらっしゃいます。

http://rubyforge.org/projects/ruby-debug/

少し使ってみましたが、フック関数が設定されてないうちは、バックトレースがうまく取れないようですね。
まあ、それでも十分に役に立つデバッガだと思います。

test.rb:
 require 'rubygems'
 require 'ruby-debug'
 
 def hoge()
   debugger
   p "hoge"
 end
 hoge()
 hoge()
 $ ruby test.rb
 test.rb:6 p "hoge"
 (rdb:1) backtrace
 --> #0 test.rb:6 in 'hoge'
 (rdb:1) c
 "hoge"
 test.rb:6 p "hoge"
 (rdb:1) backtrace
 --> #0 test.rb:6 in 'hoge'
     #1 test.rb:10
 (rdb:1)

*1:http://www.python.jp/doc/2.3.5/api/profiling.html

*2:http://docs.python.org/lib/debugger-hooks.html (英語)より抜粋

*3:http://www.ruby-lang.org/ja/man/?cmd=view;name=%C1%C8%A4%DF%B9%FE%A4%DF%B4%D8%BF%F4 より抜粋

*4:Kenel#set_trace_funcではなく、さらに低レベルのeval.cのrb_add_event_hookを使うようですね