debugger's archives

NetBeans on windowsでLinux上のrubyプログラムをリモートデバッグするためのproxy

by ginriki | 9 月 27th, 2010 

NetBeansのリモートデバッグについて、以前の記事で、以下のように書きましたが、

注意点としては、リモートと同じフルパス上にソースコードを置く必要がある点です。リモート先で、/var/www/railsprjにRoRのプロジェクトコードが置いてあるなら、ローカルでも/var/www/railsprjにプロジェクトコードを置く必要があります。

このままだと、Windows上のNetBeansでLinux上のRailsがリモートデバッグできません。
そこで、NetBeansとFast Debugger(ruby-debug-ide)の間に挟むproxyを用意して、proxyの中でファイルパス変換することでリモートデバッグできるようにしました。1

せっかくなので、以下にproxyのソースコードを置いておきます(MIT Licenseにしときました)。

以下の構成で、基本的な機能(変数チェック、ステップ実行、breakpointセット辺り)が動作するのは確認しました。
Railsがデバッグできるかどうかは、まだ試してません。

[NetBeans (Debuger GUI)] on Windows
  ↓↑
[proxy] on Windows/Linux
  ↓↑
[ruby process (+ruby-debug-ide)]  on Linux

・CRuby 1.8.7 および JRuby 1.5.1でproxy動作確認した。

なお、実際にデバッグするときは、NetBeansが動くマシン上にもデバッグ対象のソースコードを置かないとソースコードデバッグできません。
例えば、ソースコードは以下のように配置します。

・ruby-debug-ide側
/home/user/script
|---test.rb
\---lib
      \---somelib.rb

・NetBeans側
C:\win_script
|---test.rb
\---lib
      \---somelib.rb


proxyを使ったデバッグの仕方


以下の手順でproxyの起動やNetBeansからproxyへの接続を行えば、後は普通にデバッグできます。

  1. ruby-debug-ide付きでRuby process起動(以前の記事参照)
  2. proxy起動(Linux上で起動するか、NetBeans on Windows上で起動するかはお好みで)。
  3. NetBeansでproxyへの接続。

1, 2の手順はどちらが先でもOKです。
1は以前の記事と同じなので、2, 3の説明だけ書きます。

2. proxy起動

proxyスクリプトをオプション指定して起動するだけです。proxyスクリプトのオプションは以下の通り。

Usage: ruby-debug-ide-proxy.rb -t  [-p listen_port] [--rdbprefix prefix] [--ideprefix prefix] [-d]

Example: ruby-debug-ide-proxy.rb -t localhost --rdbprefix "/home/user/script" --ideprefix "C:\\win_script" -d

Exampleの例を説明すると、

  • localhostのruby-debug-ideに接続して(接続先portは1234。指定するときはlocalhost:6000のようにする。)、
  • proxyを経由してNetBeansにデバッグデータ(XML)を送るときは、/home/user/script –> C:\\win_script への変換を行い、
  • proxyを経由してNetBeansからデバッグコマンド(breakpointセットなど)を指示するときは、C:\\win_script –> /home/user/scriptへの変換を行い、
  • proxy実行時は、DEBUGログを出力する

という指定になります。

3. NetBeansからproxyへの接続

NetBeansのメニューから「デバッグ -> デバッガを接続」を選択し、proxyが動作するホスト名やポート番号を指定して接続してください。
NetBeansからproxyに接続した時、proxyからruby-debug-ideへの接続が自動的に行われます。

正しくデバッガ接続できた場合、以下のようなログがproxyプログラムの端末に出力されます。

I, [2010-09-27T03:43:21.124000 #2240]  INFO -- : proxy will replace /home/user/script with C:\win_script
I, [2010-09-27T03:43:21.468000 #2240]  INFO -- : proxy listens on 0.0.0.0:7000
I, [2010-09-27T03:43:24.530000 #2240]  INFO -- : proxy connects to 192.168.1.7:1234
I, [2010-09-27T03:43:24.530000 #2240]  INFO -- : debug start

後は、普通にNetBeansのデバッガGUIが使えます。
例えば、デバッガGUIからbreakpointをセットすると、以下のログがproxy端末上に出ます。(-dを指定して起動した場合のみ)

D, [2010-09-27T03:43:24.546000 #2240] DEBUG -- : (ide -> proxy) b C:\win_script\test.rb:5
D, [2010-09-27T03:43:24.546000 #2240] DEBUG -- : (proxy -> rdb) b /home/user/script/test.rb:5
D, [2010-09-27T03:43:24.562000 #2240] DEBUG -- : (rdb -> proxy) 
D, [2010-09-27T03:43:24.624000 #2240] DEBUG -- : (proxy -> ide) 

手順は以上です。

Ruby/Railsの場合、デバッガはプログラムの動作理解に使うものというのが私の認識です2。なので、Ruby/Rails全般に慣れていない人がデバッガ利用者だと思うので、手順はもう少し簡潔にしたいですが、そうするにはIDE内を修正するしかないかな・・・。

  1. 本当は、NetBeansのIDE内部でファイルパス変換するように修正するべきだと思いますが、面倒だったのでproxyにしました。 []
  2. デバッグは、printfデバッグの方が効率がいいと思ってます。 []

NetBeansからRuby on Rails(RoR)のリモートデバッグ

by ginriki | 7 月 12th, 2010 

NetBeansには、rubyのデバッガフロントエンドが同梱されています。
今回は、それを使ってRuby on Rails(以下RoR)のリモートデバッグをするためのメモです。

個人的には、RoRが動いてるマシンにsshでログインした後、emacsとrubydb3x.elを使う方がメリットがある1と思いますが、emacsの場合デバッガ用GUIはありません。GUIに慣れてる人はNetBeansの方が楽なので、そういう人向けにまとめます。

なお、NetBeansをインストールしたローカルマシンで、RoRも動かす場合のデバッグ方法は、NetBeansのwikiを参照してください。

まあ、ローカルマシン上でデバッグする場合も、リモートマシンと接続してデバッグする場合もやり方はほとんど変わりません。

どちらの場合も、デバッギプロセス(と同じプロセス上で動くruby-debug-ide)がTCP(デフォルトだと1234)をListenし、Netbeansからそこに接続してデバッグを開始します。Netbeansから接続するデバッギプロセスがlocalhostにいるか、リモート先のホストにいるかの違いしかないです。
接続後、Netbeansからruby-debug-ideへbreakpointをしかける場所などを指示します。

デバッグ方法そのものは、ローカル・リモートともに一緒ですが、リモートのホストに対してローカルのNetBeansを接続してデバッグするには、リモート先のホストで以下の作業が必要です。

  1. rubyインストール (解説略)
  2. RubyGemsインストール (解説略)
  3. RoRインストール・プロジェクト作成 (解説略)
  4. ruby-debug-ideインストール
  5. リモート先のRoRプロジェクトのソースコードを、ローカルにも展開
  6. ruby-debug-ide付きでRoRプロジェクトのWebサーバ起動

4, 5, 6の手順は、あまりネット上で見かけないので順に解説します。

ruby-debug-ideインストール

Linuxだと、gemで簡単にインストールできます。(gcc, ruby-develをインストールしておけば。)
Fedora8だと以下で終わりでした。

$ gem install ruby-debug
$ gem install ruby-debug-ide

Windows(のmswin版ruby)の場合、コンパイル環境の用意が面倒なので、コンパイル済みgemをダウンロードした上でインストールします。2

> gem install -l linecache-0.43-mswin32.gem
> gem install -l ruby-debug-base-0.10.3-mswin32.gem
> del ruby-debug-base-0.10.3-mswin32.gem
> gem install ruby-debug
> gem install ruby-debug-ide -v 0.4.6

コンパイル済みgem (linecache, ruby-debug-base)は以下2つのリンク先から
それぞれダウンロードしてください。

リモート先のRoRプロジェクトのソースコードを、ローカルにも展開

ローカルにもソースコードを置かないと、breakpointで止まった時にNetbeansにソースコードが読み込まれません。
注意点としては、リモートと同じフルパス上にソースコードを置く必要がある点です。リモート先で、/var/www/railsprjにRoRのプロジェクトコードが置いてあるなら、ローカルでも/var/www/railsprjにプロジェクトコードを置く必要があります。3

この辺の挙動理解には、NetBeans wikiのChecking debugger engine functionalityとか、ruby-debug-ide protocolとかを参考にして、実際にruby-debug-ideとtelnetで会話するといいです。

ruby-debug-ide付きでRuby on RailsプロジェクトのWebサーバ起動

以下な感じでRoR Webサーバ起動します。(-dも付けると、NetBeansとの通信時の処理が良く見えます。)

$ rdebug-ide --stop script/server -h 0.0.0.0
Fast Debugger (ruby-debug-ide 0.4.6) listens on 0.0.0.0:1234

NetBeansからruby-debug-ideに接続

NetBeans IDEのメニュー -> デバッグ -> 接続をクリックし、ダイアログにリモート先ホスト名とポート番号(1234)を指定して接続します。
–stopを指定してあるので、RoRの処理先頭でstopするはずです。
あとは、IDEからbreakpoint指定するなり、なんなり自由に操作します。
watch変数として、paramsを追加しておくといい感じです。

  1. sshのポート(TCP 22)が開いてることは普通にありますが、デバッガ・デバッギ通信用のポートがfirewallで遮断されることは良くあります。 []
  2. 最近だと、Visual C++は無料で手に入りますがmswin版rubyをコンパイルしたVisual C++と同じバージョンをインストールする必要しないと「MSC version unmatch」エラーが出てめんどくさいです。http://rubyforge.org/tracker/index.php?func=detail&aid=16774&group_id=1900&atid=7436 []
  3. WindowsとLinuxだとフルパスを一致させることが不可能なので、例えば、Windows上のNetBeansからLinux上のRoRがデバッグができないという点でかなりアレな仕様です。誰もやってないなら修正パッチ作るかな。 []

kprobes – カーネルへ動的にプローブ挿入

by ginriki | 7 月 13th, 2009 

Debug Hacksを読んでたら、kprobesっていうデバッグツールの話があった。
自分でモジュール作ってinsmodするだけで、カーネル再コンパイルなしで、ある実行地点でのカーネル内変数を調べるツールだそうで。

プロセスのユーザ空間(=デバッギのユーザ空間)に対してデバッガプロセスがプローブする場合は1

  1. デバッガプロセスがptrace辺りを使ってデバッギのメモリ空間上の指定アドレス上にあるアセンブリ命令をint 3に書き変えて2
  2. デバッギプロセス実行中にそのint 3命令実行して発生したSIGTRAPをデバッガプロセスがフック(=これも事前にptraceで設定する)して
  3. デバッガプロセスがptraceでデバッギプロセスのメモリ空間から値をread

っていう流れが普通だと思う。kprobes.cのコメントを見てみると、kprobesの場合もカーネルの指定命令アドレスにint 3を入れてるらしい。

/*
 * Called after single-stepping.  p->addr is the address of the
 * instruction whose first byte has been replaced by the "int 3"
 * instruction.  To avoid the SMP problems that can occur when we
 * temporarily put back the original opcode to single-step, we
 * single-stepped a copy of the instruction.  The address of this
 * copy is p->ainsn.insn. ...
 * ...
 */

ただ、SMPの関係で色々対策が必要なんだね3。int 3を実行した後、例外ベクタにジャンプしてからどういう手順で処理してるかについてイメージがわかないから、今度ソース読んでみようかな。

あと、kprobesだと見たい変数(とくにローカル変数などCPUアーキテクチャとデバッグ情報からアドレスを割り出す必要のあるもの)のアドレスを探すのが大変だけど、
kprobes+GDBでそこを解決しようとしてるのがtracepointsってやつらしい。ふむふむ。

  1. 以前、デバッガについて書いた記事もどうぞ。 []
  2. int 3はIA-32とかIntel architectureの場合の話です []
  3. デバッギプロセスにプローブする場合に必要なマルチスレッド対策とどの程度一緒なのかは、まだよくわからない。 []

crashコマンド使ってみた

by ginriki | 7 月 13th, 2009 

Kernelのコアダンプファイルを解析するのに、crashコマンドが便利だという話を聞いたので、練習として触ってみました。

手元にCentOS 5のマシンがあるので、それを使います。

解析にあたってkernelのデバッグ情報がいるので、debuginfoのRPMをインストールします1

 $ uname -a
Linux localhost.localdomain 2.6.18-128.1.16.el5 #1 SMP Tue Jun 30 06:10:28 EDT 2009 i686 i686 i386 GNU/Linux

 $ wget http://debuginfo.centos.org/5/i386/kernel-debuginfo-2.6.18-128.1.16.el5.i686.rpm
 $ wget http://debuginfo.centos.org/5/i386/kernel-debuginfo-common-2.6.18-128.1.16.el5.i686.rpm
 $ rpm -ivh kernel-debuginfo-common-2.6.18-128.1.16.el5.i686.rpm kernel-debuginfo-2.6.18-128.1.16.el5.i686.rpm

んで、crashコマンド実行。
SVR4 UNIXのcrashコマンドをベースにGDBを統合したやつだそうで2、GDBになじんでる私には結構使いやすいです。

CODE:
  1. $ crash
  2.       KERNEL: /usr/lib/debug/lib/modules/2.6.18-128.1.16.el5/vmlinux
  3.     DUMPFILE: /dev/crash
  4.         CPUS: 1
  5.         DATE: Mon Jul 13 02:17:05 2009
  6.       UPTIME: 06:52:42
  7. LOAD AVERAGE: 0.16, 0.03, 0.01
  8.        TASKS: 86
  9.     NODENAME: localhost.localdomain
  10.      RELEASE: 2.6.18-128.1.16.el5
  11.      VERSION: #1 SMP Tue Jun 30 06:10:28 EDT 2009
  12.      MACHINE: i686  (1197 Mhz)
  13.       MEMORY: 758.9 MB
  14.          PID: 3848
  15.      COMMAND: "crash"
  16.         TASK: ef67c000  [THREAD_INFO: d9120000]
  17.          CPU: 0
  18.        STATE: TASK_RUNNING (ACTIVE)
  19.  crash> set 1
  20.     PID: 1
  21. COMMAND: "init"
  22.    TASK: c16f5aa0  [THREAD_INFO: c16f6000]
  23.     CPU: 0
  24.   STATE: TASK_INTERRUPTIBLE
  25.  crash> p $tmp = jiffies
  26.   $1 = 24873000
  27.  crash> x modprobe_path
  28.   0xc0680be0 <modprobe_path>:     das
  29.  crash> show convenience
  30.    $tmp = 24873000
  31.    $__ = void
  32.    $_ = (examine_i_type *) 0xc0680be0 "/sbin/modprobe"
  33.  crash> exit

gdbスクリプトをsourceで読み込むこともできる。
まだ、まともな使い方は全然してないけど、Debug Hacksにいろいろ書いてあるので、それを読みながらやる予定。

Debug Hacks -デバッグを極めるテクニック&ツール Debug Hacks -デバッグを極めるテクニック&ツール
レビューを見る
欲しいものリストに追加
価格:3456円 在庫あり。powered by Amazon.co.jp

  1. 最初、yumでinstallしたら、なぜか2.6.18-92.1.6.el5.centos.plusが入りました。kernelバージョンと合ってないので、rpmコマンドで入れなおし。 []
  2. Crash WhitepaperのAbstract参照 []

IA-32のjmp, call命令でTrapするGDBスクリプト

by ginriki | 12 月 3rd, 2008 

jmp/call命令の実行直前までプログラムを進めるGDBスクリプトです。
こういう用途に使えるGDBコマンドがないようなので、自分で作りました。
必要な方はご自由に使ってください。
- script.gdb

使い方は以下の通り、

$ gdb a.out
(gdb) source script.gdb
(gdb) break main
(gdb) run
# main関数でbreak

(gdb) run_until_call_jmp
# main関数内のcall or jmp命令直前で停止

jmp,call命令のop codeの調べ方

IA-32命令セットにおける、JMP, CALLのオペコードは下記の通りになりますIA-32 インテル アーキテクチャ・ソフトウェア・ディベロッパーズ・マニュアル 中巻より抜粋。http://www.intel.com/jp/download/index.htm

JMP命令のオペコード
callオペコード

cb、cw、cd、cpは、オペコードの後に続く1,2,4,6バイトの値です。
jmpやcall命令では、jmp/call先のアドレス値を表しています。

/2とか/3とかっていうのは、ModR/Mバイトのdigit(= op codeの一部) 上記マニュアルの2章(特に2.4~2.6)参照を表してます。
op codeの一部なので、当然、命令を判定する際にdigitのチェックが必要です。

digitは/0~/7まであり、ModR/Mバイトの下位3bitに対応します。

例えば32bitの絶対間接nearジャンプのオペコードは下記になります。

16進表記:  FF /4
2進表記 :  11111111 xxxxx100

xxxxxの部分の値によって、ジャンプ先のアドレス値として参照するレジスタ/メモリアドレスが変化します。
例えば、eaxレジスタに格納されたアドレス値へジャンプする場合は、xxxxx=11000 です。

こんな感じで命令のop codeを調べて実装したのが上記のGDBスクリプトです。
スクリプトを改造する時の参考にしてください。

Bilingual Debuggerプロジェクト紹介(だけしとく・・・)

by ginriki | 1 月 24th, 2008 

ひさびさ。

実はだいぶ前なんですが、Sourceforge.jpにプロジェクト立ち上げました。

http://sourceforge.jp/projects/bdbg

C/C++とRubyという2つの言語のデバッガ(gdbとrdb)を切り替えて使うことを可能にするツールをアップしてあります。

VC# 2003以降のデバッガに備わっている、C#とC/C++デバッガの切り替え機能を想像してもらえれば大体あってます。

詳しくはWikiに書いたマニュアルを見てください。

Fedora6でしか動作チェックしてないので、うまくコンパイルできないとかあれば、ブログコメントかSorceforgeのフォーラムにでも書いてくれれば対処します。

#返事が遅くても怒らないでくだせえ

バイト+就活+レポートのトリプル攻撃のため、このプロジェクトに割り当てる時間がないれす。

ocamldebugの調査

by ginriki | 7 月 16th, 2007 

ML言語の一種であるOcamlに付属している、ocamldebugというデバッガについて調べたのでメモ。

かなり間違っている可能性があるので、注意。

  • gdbライクなCUI
    • break,info,helpコマンド等、gdbを知っている人なら何となく使えるようにコマンド名がつけられてます
    • でも、printコマンドで関数呼び出しや代入が行えないなど*1、gdbと違う点もあるので注意
  • breakで停止できるのは、eventと呼ばれる箇所でのみ
  • debuggerとdebugeeは別プロセス
    • Unixだと、BSDソケットを使ってプロセス間通信することでdebugee上に存在する変数の値の参照等を行っている。ここらへんは、Javaデバッガの実装に近い
  • 日本語の処理関係は、まだ弱いらしい
  • mlコード内のprint_int, print_string等の標準出力への出力用関数の処理は、ocamldebugでステップ実行している間は実際には行われない
    • debugeeの実行が終了した段階で出力される
    • 出力をバッファしてるのかな?
    • おそらく、ファイル出力やTCP等によるデータ送信も同様
    • タイムトラベル機能(後述)のためにこのような実装になってる?
    • ファイルからの入力がどうなるかはまだ調べてない
  • 変数の値等の状態も含めて実行を前の地点まで巻き戻して、そこから再実行する機能がある(タイムトラベル機能)
    • ocamldebugが適切なタイミングでcheckpointを取り、それを利用して実行の巻き戻しを行う
    • 巻き戻した地点がcheckpointを取った地点と一致しない場合は、最寄のcheckpointからその地点まで再実行して停止?
    • checkpointを取るための実装にはforkを使ってる*2
    • プロセスIDが巻き戻す前と後で違うなど、完全に状態を巻き戻すことはできないため、いやらしいプログラムの実行を巻き戻すと挙動がおかしくなった

ocamldebugはOcamlで実装されているので、私には読めん・・・

*1http://caml.inria.fr/pub/docs/manual-ocaml.bak/manual030.htmlの16.7項に書かれているBNF記法を見る限りできないよなあ・・・たぶん

*2http://caml.inria.fr/pub/docs/manual-ocaml/manual030.htmlのTurning reverse execution on and offの項目に書いてある

補足

by ginriki | 6 月 5th, 2007 

バックトレースがうまくとれない件ですが、これに対処しようとした場合、test.rbのdebuggerメソッドでbreakした瞬間に、Ruby言語上のstack frameに関するデータ構造をアセンブラ(C言語)レベルで読み込んで解析する必要があると思います。

でも、これを言語実装者側のサポートなしに実現するのはかなり大変でしょう。大変な理由を思いつく限りで列挙すると、

  • Rubyのstack frameを拡張ライブラリが直接読み込むことができない?
    • Rubyの実装に詳しいわけではないので、直接読み込めないというのは勘なのですが、セキュリティの観点から考えて、拡張ライブラリがstack frameをいじれないのは当然だと思う
  • debuggerとdebuggeeが同一プロセスであるため、Rubyのstack frameに直接アクセスできたとしても、stackのどの部分がdebuggerのもので、どの部分がdebuggeeのものか区別が難しい
    • 同一プロセス上にあるから、gdbで言うところのprintコマンドが簡単に実現できるという利点もあるのですが、バックトレースは実装しにくくなります
    • debuggerとdebuggeeが同一プロセスであるというのは、C, Javaのデバッガと大きく異なる点でもあります

こんな感じでしょうか。

まあ、PythonやRubyの場合は、printやpを使ってデバッグしろってことでしょうかねえ。

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

by ginriki | 6 月 5th, 2007 

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

Pythonには、pdb。 Rubyには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)

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

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

*3http://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を使うようですね

デバッガの実装(Java編)

by ginriki | 6 月 1st, 2007 

次はJavaのデバッガについて調べました。

背景

Javaプログラムの実行は、Java Virtual Machine(以下JVM)上に存在する仮想的なCPUがbytecode(仮想的なCPUが解釈する機械語)を解釈、処理をしていくことによって、行われます。

bytecodeはJavaコードをjavacでコンパイルしたときに生成されるコードでもあります。

JavaコードとCコード、bytecodeとアセンブリコードが、それぞれ似たような存在です。

OSから見た場合、JVMとJVM上で動くJavaプログラムは一つのプロセスにすぎません。

よって、OSが提供するデバッグAPIを用いたデバッガでJavaプログラムをデバッグすることが可能ですが、JVMの実行コード(アセンブリコード)やJVMが扱うデータ、JVM上で動くJavaプログラムの実行コード(bytecode)やJavaプログラムが扱うデータがごちゃごちゃしていて、デバッグするのは大変です。

JVM自身に関する情報とJavaプログラムに関する情報を切り分けて、デバッガ利用者へ提示するようにデバッガを実装すれば良いのですが、JVMの実装が変更になった時、それにあわせてデバッガを実装しなおす必要がでてきます。

また、実装の異なる複数のJVMに対応したデバッガを用意するのは困難ですし、デバッグAPIはOS依存なので、複数のOSで動作するデバッガを用意するのも困難です。

JPDAとそれを利用したデバッガ実装

そこで、Sunは、以下の3つの仕様を定めることで、JVMやOSの違いによる問題を解消しています*1

  • JVMTI(Virtual Machine Debug Interface)
    • JVMが提供するデバッグ機能の仕様
      • Javaプログラム上の変数情報の要求や、breakpointイベントの発生通知等の機能等
  • JDWP(Java Debug Wire Protocol)
    • debugeeとdebuggerでやり取りされるデバッグ情報や要求の形式に関する仕様
  • JDI(Java Debug Interface)
    • debugger側の実装に利用するpure Javaのインターフェイスに関する仕様

これら3つの仕様を総称してJPDA(Java Platform Debugger Architecture)と呼びます。

Sun自身が、各仕様を満たした、リファレンス実装を提供しているので、デバッガ製作者はJDIを利用して、主にユーザインターフェイス部分を実装するだけでデバッガが作れるようです。

また、JVMの実装者は、JVMTIの仕様を満たすようにJVMを実装し、JDWPとJDIに関する実装はSunのリファレンス実装を利用すれば、新しく実装したJVM上で動作するJavaプログラムのデバッグを、既存のデバッガで行うことができるんでしょう(たぶん)。

メモ

  • JPDAに関するドキュメント

http://sdc.sun.co.jp/java/docs/j2se/1.4/ja/docs/ja/guide/jpda/index.html

  • Java Debugger
    • CUIベース
      • jdb
      • jdebug
    • GUIベース
      • jswat
  • JVMの種類
    • Sun JVM
    • IBM JVM
    • WebLogic JRockit JVM
    • Microsoft JVM

*1:Java2 sdk 1.4時点

ブログで紹介した商品

  • Image of デバッガの理論と実装 (ASCII SOFTWARE SCIENCE Language)