[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

17. デバッグ

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Debugging"
"intro/デバッグ"へのコメント(無し)

GNU Emacsには2つのデバッガ、debugedebugがある。 最初のものは、Emacs内部に組み込まれていていつでも使える。 2番目のものはEmacsに対する拡張であり、 第19版で標準ディストリビューションの一部になった。

どちらのデバッガも節 `Debugging Lisp Programs' in

GNU Emacs Lispリファレンスマニュアル
に詳しく記載されている。 本章では、それぞれの短い例を示すことにする。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

17.1 debug

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=debug"
"intro/debug"へのコメント(無し)

1から指定した数までの総和を求める関数定義を書いたとしよう (まえに説明した関数triangleである。 説明は、See 節 減少カウンタの例)。

しかし、関数定義にはバグがある。 `1-'を`1='とタイプミスした。 まちがった定義はつぎのとおりである。

 
(defun triangle-bugged (number)
  "Return sum of numbers 1 through NUMBER inclusive."
  (let ((total 0))
    (while (> number 0)
      (setq total (+ total number))
      (setq number (1= number)))      ; ここがエラー
    total))

Infoで読んでいる場合には、いつものようにこの定義を評価できる。 エコー領域にtriangle-buggedと表示される。

引数4で関数triangle-buggedを評価してみる。

 
(triangle-bugged 4)

 
つぎのようなエラーメッセージが出る。

Symbol's function definition is void: 1=

実際、このような単純なバグならば、このエラーメッセージから 定義を修正するために必要なことがらがわかる。 しかし、何が起こっているのかわからなかったとしよう。

debug-on-errorの値をtに設定してデバッガを有効にする。

 
(setq debug-on-error t)

これは、Emacsにつぎにエラーを検出したらデバッガに入るように指示する。

debug-on-errornilを設定すればデバッガを無効にできる。

 
(setq debug-on-error nil)

debug-on-errortを設定し、つぎの式を評価する。

 
(triangle-bugged 4)

こんどは、Emacsは、つぎのようなバッファ`*Backtrace*'を作成する。

 
---------- Buffer: *Backtrace* ----------
Signalling: (void-function 1=)
  (1= number))
  (setq number (1= number)))
  (while (> number 0) (setq total (+ total number))
        (setq number (1= number))))
  (let ((total 0)) (while (> number 0) (setq total ...)
        (setq number ...)) total))
  triangle-bugged(4)
  eval((triangle-bugged 4))
  eval-last-sexp(nil)
* call-interactively(eval-last-sexp)
---------- Buffer: *Backtrace* ----------

(この例は少々整形してある。 デバッガは長い行を折り畳まない。)

バッファ`*Backtrace*'は、下から上へ向けて読む。 Emacsが何を行ってエラーに至ったかを教えてくれる。 この場合は、EmacsはC-x C-eeval-last-sexp)を対話的に呼び出し、 これはtriangle-bugged式の評価につながった。 各行はLispインタープリタが、つぎに何を評価したのかを教えてくれる。

バッファの先頭から3行目はつぎのとおりである。

 
(setq number (1= number))

Emacsはこの式を評価しようとした。 そのために、先頭から2行目に示されたもっとも内側の式を評価しようとした。

 
(1= number)

これがエラーを起こした場所であり、 先頭の行にはつぎのように表示されている。

 
Signalling: (void-function 1=)

この誤りを修正して、関数定義を再評価し、再度試せばよい。

Infoで読んでいる場合には、 debug-on-errornilを設定してデバッガを無効にする。

 
(setq debug-on-error nil)



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

17.2 debug-on-entry

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=debug-on-entry"
"intro/debug-on-entry"へのコメント(無し)

関数に対してdebugを起動する2番目の方法は、 関数を呼び出したときにデバッガに入ることである。 これにはdebug-on-entryを呼ぶ。

つぎのようにタイプする。

 
M-x debug-on-entry RET triangle-bugged RET

続いて、つぎの式を評価する。

 
(triangle-bugged 5)

Emacsはバッファ`*Backtrace*'を作成し、 関数triangle-buggedを評価し始めたことを伝える。

 
---------- Buffer: *Backtrace* ----------
Entering:
* triangle-bugged(5)
  eval((triangle-bugged 5))
  eval-last-sexp(nil)
* call-interactively(eval-last-sexp)
---------- Buffer: *Backtrace* ----------

バッファ`*Backtrace*'の中で、dとタイプする。 Emacsはtriangle-buggedの最初の式を評価し、 バッファはつぎのようになる。

 
---------- Buffer: *Backtrace* ----------
Beginning evaluation of function call form:
* (let ((total 0)) (while (> number 0) (setq total ...)
        (setq number ...)) total))
  triangle-bugged(5)
* eval((triangle-bugged 5))
  eval-last-sexp(nil)
* call-interactively(eval-last-sexp)
---------- Buffer: *Backtrace* ----------

さらにdをゆっくりと8回タイプする。 dをタイプするたびに、Emacsは関数定義内の別の式を評価する。 最終的に、バッファはつぎのようになる。

 
---------- Buffer: *Backtrace* ----------
Beginning evaluation of function call form:
* (setq number (1= number)))
* (while (> number 0) (setq total (+ total number)) 
        (setq number (1= number))))
* (let ((total 0)) (while (> number 0) 
        (setq total ...) (setq number ...)) total))
  triangle-bugged(5)
* eval((triangle-bugged 5))
  eval-last-sexp(nil)
* call-interactively(eval-last-sexp)
---------- Buffer: *Backtrace* ----------

最後にさらにdを2回タイプすると、 Emacsはエラーに到達し、バッファ`*Backtrace*'の先頭の2行には つぎのように表示される。

 
---------- Buffer: *Backtrace* ----------
Signalling: (void-function 1=)
* (1= number))
...
---------- Buffer: *Backtrace* ----------

dをタイプすることで、関数を順を追って実行することができるのである。

バッファ`*Backtrace*'を終えるには、qとタイプする。 これは、関数の追跡を止めるが、debug-on-entryを取り消さない。

debug-on-entryの効果を無効にするには、 つぎのように、cancel-debug-on-entryを呼んで関数名を与える。

 
M-x cancel-debug-on-entry RET triangle-debugged RET

(Infoで読んでいる場合には、ここでdebug-on-entryを取り消す。)



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

17.3 debug-on-quit(debug)

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=debug-on-quit"
"intro/debug-on-quit(debug)"へのコメント(無し)

debug-on-errorを設定したりdebug-on-entryを呼んだりすることに 加えて、debugを起動するには別に2つの方法がある。

変数debug-on-quittに設定すると、 C-gkeyboard-quit)とタイプすると いつでもdebugを起動できる。 これは、無限ループのデバッグに役立つ。

あるいは、つぎのように、コード内のデバッガを起動したい場所に(debug)を 入れておくことである。

 
(defun triangle-bugged (number)
  "Return sum of numbers 1 through NUMBER inclusive."
  (let ((total 0))
    (while (> number 0)
      (setq total (+ total number))
      (debug)                         ; デバッガを起動
      (setq number (1= number)))      ; ここがエラー
    total))

関数debugは、節 `The Lisp Debugger' in

GNU Emacs Lispリファレンスマニュアル
に詳しく記載されている。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

17.4 ソースレベルのデバッガedebug

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=edebug"
"intro/ソースレベルのデバッガedebug"へのコメント(無し)

Edebugはデバッグ中のソースコードを表示して、 現在評価中の行の左端に矢印を表示する。

関数の実行を行ごとに制御したり、実行を停止する ブレークポイント(breakpoint)まで素早く実行させたりできる。

Edebugは、節 `Edebug' in

GNU Emacs Lispリファレンスマニュアル
に記載されている。

バグのあるtriangle-recursivelyの関数定義をつぎに示す。 復習するには、See 節 11.2.2 カウンタの代用としての再帰。 以下で説明するが、この例ではdefunの字下げを書いてない。

 
(defun triangle-recursively-bugged (number)
  "Return sum of numbers 1 through NUMBER inclusive.
Uses recursion."
  (if (= number 1)                   
      1                              
    (+ number                        
       (triangle-recursively-bugged   
        (1= number)))))               ; ここがエラー

この定義をインストールするには、普通は、関数の閉じ括弧の直後にカーソルを 置いてC-x C-eeval-last-sexp)とタイプするか、 あるいは、定義の途中にカーソルを置いてC-M-xeval-defun) とタイプする (デフォルトでは、コマンドeval-defunは、Emacs Lispモードか Lisp Interactiveモードのみで使える)。

しかし、この関数定義をEdebugで使うには準備する必要があり、 別のコマンドを使ってコードを処置する必要がある。 Emacs第19版では、定義の途中にカーソルを置いてつぎのようにタイプする。

 
M-x edebug-defun RET

これにより、Emacsは必要ならばEdebugを自動的にロードし、 関数を正しく処置する (Edebugをロードしたあとは、C-u C-M-x (前置引数を指定したeval-defun)などの標準的なキーバインドで edebug-defunを呼び出せる)。

Emacs第18版では、読者自身がEdebugをロードする必要がある。 `.emacs'ファイルに適切なloadコマンドを入れておく。

Infoで読んでいる場合には、上に示した関数triangle-recursively-buggedを 処置できる。 edebug-defunは、defunの行が字下げされていると 定義の切れ目を検出できない。 そのために、例ではdefunの左側の空白を取ってある。

関数を処置したら、つぎの式の直後にカーソルを置いて C-x C-eeval-last-sexp)とタイプする。

 
(triangle-recursively-bugged 3)

triangle-recursively-buggedのソースに戻り、 カーソルが関数のifの行の先頭に位置付けされる。 さらに、その行の左端に`=>'のような矢印が表示される。 矢印は、関数のどの行を実行中かを示す。

 
=>-!-(if (= number 1)

In the example, the location of point is displayed as `-!-' (in a printed book, it is displayed with a five pointed star).

SPCを押すと、ポイントはつぎに実行すべき式へ移動し、 つぎのようになる。

 
=>(if -!-(= number 1)

SPCを押し続けると、ポイントは式のあいだを移動する。 同時に、式が値を返すと、その値がエコー領域に表示される。 たとえば、numberのあとにポイントが移動したときには、 つぎのように表示される。

 
Result: 3 = C-c

これは、numberの値は3であり、ASCIIコードではCTL-C (アルファベットの3番目の文字)であることを意味する。

エラーに達するまで、コードの中を移動できる。 評価するまえには、その行はつぎのようである。

 
=>        -!-(1= number)))))               ; ここがエラー

もう一度SPCを押すと、つぎのようなエラーメッセージが表示される。

 
Symbol's function definition is void: 1=

これがバグである。

`q'を押してEdebugを終了する。

処置した関数定義を削除するには、 単に処置しない普通のコマンドで関数定義を再評価すればよい。 たとえば、関数定義の閉じ括弧の直後にカーソルを 移動してC-x C-eとタイプする。

Edebugでは、関数の中を動き廻るよりも多くのことができる。 エラーを検出したり指定した停止位置に達した場合だけ停止するとか、 さまざまな式の値の変化を表示したり、 関数が何回呼ばれたかを調べたりなどである。

Edebugは節 `Edebug' in

GNU Emacs Lispリファレンスマニュアル
に記載されている。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

17.5 デバッグの演習問題

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Debugging%20Exercises"
"intro/デバッグの演習問題"へのコメント(無し)


[ << ] [ >> ]           [表紙] [目次] [索引] [検索] [上端 / 下端] [?]