Top > Ruby > Debug

コマンドラインでデバッガを使ってみる

 Ruby には、標準でデバッガが付いています。デバッガは Ruby で記述されています。

$ ruby -r debug filename.rb

 このコマンドを実行すると、filename.rb のデバッグ、という意味になります。

では、下記のスクリプトを test.rb として保存し、デバッガで起動してみましょう。

  0
  1
  2
  3
  4
  5
  6
  7
  8
  9
#!/usr/bin/env ruby
 
for i in 1..1000
  j = i * i
  printf("%04d^2 = %07d / ", i, j)
 
  if(i % 4 == 0) then
    printf("\n")
  end
end

 下記のような画面になるはずです。

Debug.rb
Emacs support available.

test.rb:3:for i in 1..1000
(rdb:1) _           ← コマンド入力待ちのプロンプト

 プロンプトが、test.rb の3行目にある 「for i in 1..1000」の行の実行直前であることを示しています。つまり、このスクリプトでいちばん始めに解釈されるのは3行目、ということになります。3行目がぴんとこない場合は、「list num」(num は任意の数字) を入れると、前後5行分のソースコードを表示することができます。(list には省略形 l があるので、l num としても OK です。l は L の小文字なのですが、数字のいち(1) とまぎらわしいのであえて list にしてます)

test.rb:3:for i in 1..1000
(rdb:1) list 3[Enter]
[-2, 7] in test.rb
   1
   2
=> 3  for i in 1..1000
   4    j = i * i
   5    printf("%04d^2 = %07d / ", i, j)
   6
   7    if(i % 4 == 0) then
(rdb:1) _

 → (=> 表記) で現在行が表示されていて、ぐっとわかりやすくなりました。さて、プログラムを1行ずつ実行してみましょう。プログラムを1行ずつ実行するには、step あるいは s (step の省略形) と入力して、Enter キーです。step を繰り返せば、それだけプログラムが進みます。

(rdb:1) s [Enter]
test.rb:4:  j = i * i
(rdb:1) s [Enter]
test.rb:5:  printf("%04d^2 = %07d / ", i, j)
(rdb:1) s [Enter]
0001^2 = 0000001 / test.rb:7:  if(i % 4 == 0) then
(rdb:1) s [Enter]
test.rb:7:  if(i % 4 == 0) then
(rdb:1) s [Enter]
test.rb:7:  if(i % 4 == 0) then
(rdb:1) s [Enter]
test.rb:4:  j = i * i
(rdb:1) _

 ちょっと注意したいのは5行目でしょうか。画面出力が伴うと、デバッガ表示が行末や次の行以降にずれてしまうことがあります。「0001^2 = 0000001 / test.rb:7: if(i % 4 == 0) then」の前半部分はスクリプト側のprintfが、後半部分の test.rb:7: 以降はデバッガが表示してるというわけですね。

 その後、7行目の if が3回表示されます。これは、「i%4」で1回、 i%4 と 0 の比較(true/false に変換)で1回、if 分岐で1回とカウントされているから、と考えられます。そして、次は4行目に戻ったということがわかります。考えてみれば、i は初期値 1 で、1 % 4 はゼロではないので、8 と 9 行目はスキップされ、10行目は for 繰り返しのブロック終了になるので、繰り返しの最初に戻った、ということですね。

 s を何回か繰り返していけば、8行目の実行の瞬間に立ち会うことができるでしょう。けれど、ちょっと待ってください。s よりももっと便利なコマンド、break (省略形 b)と cont (省略形 c) というコマンドがあるんです。break というのはブレークポイントのことで、プログラムの実行を指定行で中断させることができます。そして、cont がブレークポイントにぶつかるまで一気にプログラムをすすめるコマンドです。では、そのブレークポイントというのをまずはしかけてみましょう。

(rdb:1) list 1-10 [Enter]
[1, 10] in test.rb
   1
   2
   3  for i in 1..1000
=> 4    j = i * i
   5    printf("%04d^2 = %07d / ", i, j)
   6
   7    if(i % 4 == 0) then
   8      printf("\n")
   9    end
   10  end
(rdb:1) b 8 [Enter]
Set breakpoint 1 at test.rb:8
(rdb:1) b [Enter]
Breakpoints:
  1 test.rb:8

(rdb:1) _

 list 1-10 で、1~10行目のコードを表示し、ブレークポイントをしかけたい場所(if分岐の中身)が確かに8行目であることを再確認してます。そして、b 8 で8行目をブレークポイントとする、と指定してます。最後の b (数字無し) は、今までにしかけたブレークポイントを一覧表示するコマンドです。ちゃんと8行目にブレークポイントがしかけられていることを確認できます。では、if 分岐の中に入るまで、プログラムを一気に進めましょう。c コマンドを打ち込んでみてください。

(rdb:1) c [Enter]
0002^2 = 0000004 / 0003^2 = 0000009 / 0004^2 = 0000016 / Breakpoint 1, toplevel
at test.rb:8
test.rb:8:    printf("\n")
(rdb:1) _

 ちゃんと8行目の実行直前で一時停止状態になりましたね。list すれば現在位置が if の中、ということを再確認できます。printf の表示によると、i は 4 で j は 16 ですね。 i は 4 で、4 % 4 はゼロですので if の中に入ってきましたよ、と。では、i や j は本当に printf の通りの数値なのでしょうか?

 変数については、p 変数名 で表示が可能です。もっとも、変数はオブジェクトですから、実際にはクラスやメソッドの返値に応用することもできます。

(rdb:1) p i [Enter]
4
(rdb:1) p j [Enter]
16
(rdb:1) _

 確かに、変数は予想通りですね。もっとも、この方法だと、変数が十数個あったりしたときに大変ですので、もっと簡単に一覧表示する方法があります。それが、var local (省略名 v l) です。

(rdb:1) v l [Enter]
  i => 4
  j => 16
(rdb:1) _

 puts や printf をしないでも変数をいつでも確認できるようになると、「j が 1000 を超える瞬間はいつなのかな」といったようなことを調べてみたくなるかもしれません。j が 100 になるのは i が 10 のとき (10*10) と思い浮かびますが、このようなケースの場合は暗算ですぐに出るものではありません。そして、プログラムのデバッグでは暗算や電卓では推し量れないことも、また多いのです。このようなケースの場合は、watch 条件式 (省略形は wat 条件式)という、「行番号ではないもうひとつのブレークポイント(ウォッチポイント と言うそうです)」を仕掛けることにより解決することができます。今回の場合は、wat (j > 1000) というコマンドを実行すればいいことになります。

(rdb:1) wat (j > 1000) [Enter]
Set watchpoint 2:(j > 1000)
(rdb:1) _

では、c と v l で、いざ 1000 超えの世界へ...おや?

(rdb:1) c [Enter]

0005^2 = 0000025 / 0006^2 = 0000036 / 0007^2 = 0000049 / 0008^2 = 0000064 / Brea
kpoint 1, toplevel at test.rb:8
test.rb:8:    printf("\n")
(rdb:1) v l [Enter]
  i => 8
  j => 64
(rdb:1) _

 j が 64 なのに止まってしまいました。なぜなんでしょう?ちょっと考えてみましょう。

 犯人は、b コマンドでわかりますよ。

(rdb:1) b [Enter]
Breakpoints:
  1 test.rb:8

Watchpoints:
  2 (j > 1000)

(rdb:1) _

 これを見ると、ウォッチポイントもブレークポイントの仲間扱いされていることがわかりますが、注目なのは 1 test.rb:8 です。初めてしかけたブレークポイントですね。そして、c コマンドはブレークポイントにぶつかったら実行中断してしまいます。j > 1000 よりも前に、8行目に入ってくる機会がたくさんあるので、次の i % 4 == 0 である i は 8 のときにブレークポイントに捕まってしまった、というわけです。でも、このままじゃあ j > 1000 にくるまでに何回8行目で止まるか検討がつかないですよね。ということで、最初のブレークポイントを削除してしまいましょう。ブレークポイントの削除は delete num (省略形 del num) と指定します。注意したい点はふたつあって、ひとつは num には行数ではなく、b でリストアップされた一番左側の番号を指定すると言うことがまずひとつ、もう一つは数字を指定し忘れると「全ブレークポイント削除」になってしまう、ということです。では、8行目指定のブレークポイントを削除(del 1) して、続きを実行(c) してみましょうか。

(rdb:1) del 1  [Enter]
(rdb:1) b [Enter]
Breakpoints:

Watchpoints:
 2 (j > 1000)

(rdb:1) c [Enter]

0009^2 = 0000081 / 0010^2 = 0000100 / 0011^2 = 0000121 / 0012^2 = 0000144 /
0013^2 = 0000169 / 0014^2 = 0000196 / 0015^2 = 0000225 / 0016^2 = 0000256 /
0017^2 = 0000289 / 0018^2 = 0000324 / 0019^2 = 0000361 / 0020^2 = 0000400 /
0021^2 = 0000441 / 0022^2 = 0000484 / 0023^2 = 0000529 / 0024^2 = 0000576 /
0025^2 = 0000625 / 0026^2 = 0000676 / 0027^2 = 0000729 / 0028^2 = 0000784 /
0029^2 = 0000841 / 0030^2 = 0000900 / 0031^2 = 0000961 / Watchpoint 2, toplevel
at test.rb:5
test.rb:5:  printf("%04d^2 = %07d / ", i, j)
(rdb:1) v l [Enter]
  i => 32
  j => 1024
(rdb:1) list [Enter]
[0, 9] in test.rb
   1
   2
   3  for i in 1..1000
   4    j = i * i
=> 5    printf("%04d^2 = %07d / ", i, j)
   6
   7    if(i % 4 == 0) then
   8      printf("\n")
   9    end
(rdb:1) _

 以上の結果から、i が 32 になって、j に 32*32 が代入された直後 (j が 1024 になった)に一時停止になったことを確認できます。

 さて、デバッグは終了して別のことをやりたくなってきました。そんなときは quit (省略形 q) コマンドを実行すれば、y/n の確認を経てデバッグ終了できます。

(rdb:1) q [Enter]
Really quit? (y/n) y [Enter]

コマンドラインのデバッガのヘルプを見る

 コマンドの使い方をちょっと調べたいとき、help (省略形 h) というコマンドの実行により、簡易リファレンスが表示されます。ここでは踏み込んで説明しなかった「グローバル変数の表示」から、「スレッドのデバッグ」「フレームの位置確認や上下移動」といったコマンドの存在も確認できますね。

(rdb:1) h [Enter]
Debugger help v.-0.002b
Commands
  b[reak] [file|class:]<line|method>
  b[reak] [class.]<line|method>
                             set breakpoint to some position
  wat[ch] <expression>       set watchpoint to some expression
  cat[ch] <an Exception>     set catchpoint to an exception
  b[reak]                    list breakpoints
  cat[ch]                    show catchpoint
  :
  : (表示中略)
  : 
  th[read] stop <nnn>        stop thread nnn
  th[read] resume <nnn>      resume thread nnn
  p expression               evaluate expression and print its value
  h[elp]                     print this help
  <everything else>          evaluate
(rdb:1) _

 英語は苦手で...という方は、せっかくの国産スクリプト言語なので、日本語ドキュメントを活用しちゃいましょう。ちゃんと Rubyリファレンスマニュアルのdebug項 にコマンドの一覧と詳細が掲載されていますので。

簡易リファレンス

 とりあえず、簡単にまとめてみました。クラスやスレッドを使いこなすプログラミングをする場合は、ここにリストアップしていない thread や method や var instance <object> 等のコマンドを使わなければ行けなくなるかもしれません。とりあえず、最初のうちは quit list break cont step p を覚えておけば、あまり苦労しないかなと思います。

コマンド名省略形意味
デバッガ関連
quitqデバッガの終了
helphコマンドのリファレンス表示
ソースコード位置把握
list numl numnum行目±5行のソースを表示
list num1-num2l num1-num2num1行目~num2行目のソースを表示
wherew現在位置を表示。関数やメソッドの中であれば、呼び出し元も表示する。
変数やメソッド返値の確認
p 変数名変数の内容を表示する
p メソッド名(引数)メソッドの返値を表示する
var localv lローカル変数を一覧表示する
var globalv gグローバル変数を一覧表示する
プログラムの実行を進める
steps1行ずつプログラムの実行を進める
contcブレークポイント(またはウォッチポイント)の直前までプログラムを進める。ブレークポイントが未定義の場合は、プログラム終了まで進め続ける。
finishfin今のフレームを終了し、上位フレームへ移動 (繰り返しやメソッドの外側に出ます)
プログラムの実行を中断させる
break numb numnum行目にブレークポイントをしかける
breakbブレークポイントやウォッチポイントの一覧を表示する
watch 条件式wat 条件式条件式を満たしたら一時停止させる「ウォッチポイント」をしかける
delete numdel numオプション無しbreak(ブレークポイント一覧)での表示にて、一番左側が num のブレークポイントを削除する
deletedel全ブレークポイントを削除する

その他、参考になりそうな情報

  • Ruby で debug する7つの方法 (2006/10/10)
    • pp や y というコマンドもあるそうです。他にもいくつかこのWIKIで書いていなかったこと、私の知らなかった方法が書かれています。
    • 整理する時間ができたら、このWIKIについても Ruby 1.8.5 対応のデバッグ手順メモにアップデートしたいなあと思っています (^_-

リロード   新規 下位ページ作成 編集 凍結 差分 添付 コピー 名前変更   ホーム 一覧 検索 最終更新 バックアップ リンク元   ヘルプ   最終更新のRSS
Last-modified: Fri, 22 Jul 2011 21:57:52 JST (1525d)