2007年2月10日

スクリプト言語用のデバッガの使い方 - Ruby, Python, Perl

スクリプト言語用の CUIのデバッガの使い方を簡単にまとめました。対象言語は Ruby, Python, Perl です。

 

私は C, C++ でプログラムを書いているときはデバッガ (主に GNU/Linux 上の gdb) を頻繁に利用します。しかし、スクリプト言語ではそれほどでもありません。これはおそらく次のような理由によります。

  • ビルドが不要なので printf デバッグが容易 (ある程度大きい C++ のプログラムではビルド時間が長いので printf の挿入はしんどい)
  • 異常終了時にスタックトレースが表示される (Ruby, Python なら自動、Perl の場合は use Carp; $SIG{__DIE__} = \&Carp::confess; など)
  • オブジェクトのインスペクトが簡単 (Ruby なら p/pp, Python なら print, Perl なら Data::Dumper)
  • セグメンテーションフォルトは (基本的に) 発生しない
  • 小さい単純なプログラムを書いている場合が多い

しかしながら、 /usr/lib の下にインストールされている標準ライブラリの挙動を調査するときや、他人が書いたプログラムを追いかけるときなどはデバッガが重宝します。といいつつ、いざというときにたまに使おうとすると使い方を忘れていることが多いので、ここに簡単にまとめておくことにしました。

サンプルプログラムにはすべて「http://0xcc.net/ の中身を取得して表示する」という短いコードを用いました。

Ruby

Ruby には標準で debug.rb というスクリプトが付属しています。debug.rb を使ってデバッグするにはコマンドラインから次のように実行します。

% ruby -rdebug デバッグ対象のRubyプログラム

使い方はほとんど gdb とほぼ同じですが、 run/start に相当するコマンドはなく、デバッギ(デバッグの対象のプログラム) は1行目で止まっている状態から始まります。help コマンドでヘルプを表示できます。

使用例

以下の例では test.rb をデバッギとして走らせ、次の操作を行っています。

  • b(reak) コマンドで test.rb の 2行目にブレークポイントを設定
  • c(ont) コマンドでブレークポイントまで実行を進める
  • s(step) コマンドで open メソッドの中に進む
  • l(ist) コマンドで現在位置の前後のコードを表示
  • p コマンドで変数 (name) の中身を表示
  • w(where) コマンドでスタックトレースを表示
  • q(uit) コマンドで終了

デバッグ対象のプログラム

require 'open-uri'
f = open("http://0xcc.net/", "r")
puts f.read

デバッグセッション

% ruby -rdebug test.rb
Debug.rb
Emacs support available.

test.rb:1:require 'open-uri'
(rdb:1) b 2
Set breakpoint 1 at test.rb:2
(rdb:1) c
Breakpoint 1, toplevel at test.rb:2
test.rb:2:f = open("http://0xcc.net/", "r")
(rdb:1) s
/usr/local/lib/ruby/1.9/open-uri.rb:81:    if name.respond_to?(:open)
(rdb:1) l
[76, 85] in /usr/local/lib/ruby/1.9/open-uri.rb
   76    # URI::FTP#open,
   77    # Kernel[#.]open can accepts such URIs and strings which begins with
   78    # http://, https:// and ftp://.
   79    # In these case, the opened file object is extended by OpenURI::Meta.
   80    def open(name, *rest, &block) # :doc:
=> 81      if name.respond_to?(:open)
   82        name.open(*rest, &block)
   83      elsif name.respond_to?(:to_str) &&
   84            %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ name &&
   85            (uri = URI.parse(name)).respond_to?(:open)
(rdb:1) p name
"http://0xcc.net/"
(rdb:1) w
--> #1 /usr/local/lib/ruby/1.9/open-uri.rb:81:in `open'
    #2 test.rb:2
(rdb:1) q
Really quit? (y/n) y

最初に、b で2行目にブレークポイントを設定しているのは、手元の環境 (ruby 1.8.2) では、n を実行すると、require "open-uri" の行の次に進むのではなく、なぜか open-uri.rb の中に入っていってしまったためです。

Python

Python には標準で pdb というデバッガが付属しています。pdb を使ってデバッグするにはコマンドラインから次のように実行します。

% pdb デバッグ対象のPythonプログラム

使い方はほとんど gdb とほぼ同じですが、 run/start に相当するコマンドはなく、デバッギは1行目で止まっている状態から始まります。help コマンドでヘルプを表示できます。pdb は gdb と同様に、デバッギを指定せずに単独でも実行できます。

使用例

以下の例では test.py をデバッギとして走らせ、次の操作を行っています。

  • n コマンドで test.py の 2行目まで実行を進める
  • s(step) コマンドで urlopen メソッドの中に進む
  • l(ist) コマンドで現在位置の前後のコードを表示
  • p コマンドで変数 (url) の中身を表示
  • w(where) コマンドでスタックトレースを表示
  • q(uit) コマンドで終了

デバッグ対象のプログラム

import urllib2
f = urllib2.urlopen("http://0xcc.net/")
print f.read()

デバッグセッション

% pdb  test.py
> /tmp/test.py(1)?()
-> import urllib2
(Pdb) n
> /tmp/test.py(2)?()
-> f = urllib2.urlopen("http://0xcc.net/")
(Pdb) s
--Call--
> /usr/lib/python2.4/urllib2.py(126)urlopen()
-> def urlopen(url, data=None):
(Pdb) l
121     from urllib import localhost, url2pathname, getproxies
122
123     __version__ = "2.4"
124
125     _opener = None
126  -> def urlopen(url, data=None):
127         global _opener
128         if _opener is None:
129             _opener = build_opener()
130         return _opener.open(url, data)
131
(Pdb) p url
'http://0xcc.net/'
(Pdb) w
  /usr/lib/python2.4/bdb.py(366)run()
-> exec cmd in globals, locals
  <string>(1)?()
  /tmp/test.py(2)?()
-> f = urllib2.urlopen("http://0xcc.net/")
> /usr/lib/python2.4/urllib2.py(126)urlopen()
-> def urlopen(url, data=None):
(Pdb) q

Perl

Perl には -d というデバッグ用のオプションがあります。-d を使ってデバッグするにはコマンドラインから次のように実行します。

% perl -d デバッグ対象のPerlプログラム

使い方はほとんど gdb と大体同じ感覚ですが、 run/start に相当するコマンドはなく、デバッギは1行目で止まっている状態から始まります。コマンドはすべて 1文字です。 h コマンドでヘルプを表示できます。help と打っても無視されるので注意しましょう。

perl -de 1 のように実行すると、 gdb と同様に、デバッギを指定せずに単独でも実行できます (正確には "1"というプログラムをデバッギとして用いています)。

以下の例では test.pl をデバッギとして走らせ、次の操作を行っています。

  • s コマンドで get メソッドの中に進む
  • l コマンドで現在位置の前後のコードを表示
  • p コマンドで変数 (@_) の中身を表示
  • T コマンドでスタックトレースを表示
  • q コマンドで終了

デバッグ対象のプログラム

use LWP::Simple;
my $content = get("http://0xcc.net");
print $content;

デバッグセッション

% perl -d test.pl

Loading DB routines from perl5db.pl version 1.25
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(test.pl:2):      my $content = get("http://0xcc.net");
  DB<1> s
LWP::Simple::get(/usr/share/perl5/LWP/Simple.pm:50):
50:         %loop_check = ();
  DB<1> l
50==>       %loop_check = ();
51:         goto \&_get;
52      }
53
54
55      sub get_old ($)
56      {
57:         my($url) = @_;
58:         _init_ua() unless $ua;
59
  DB<1> p @_
http://0xcc.net
  DB<2> T
$ = LWP::Simple::get('http://0xcc.net') called from file `test.pl' line 2
  DB<2> q

まとめ

Ruby, Python, Perl 用の CUI のデバッガを使う方法を紹介しました。printf デバッグがやりづらい場面で重宝するのではないかと思います。

ちなみに、 JavaScript のデバッグには Firefox で動くFirebug が便利です。Firebug は JavaScript のGUIデバッガの他にもさまざまな便利な機能を備えています。お勧めです。

謝辞

use Carp; の方法は宮川さんに教えてもらいました。ありがとうございます。