`

Programming in Emacs Lisp笔记(十七) 调试

阅读更多

调试

GNU Emacs中有两个高度器,debug和edebug。第一个是Emacs内建的可以随时使用它;第二个需要借助一些函数才能使用。

debug

假设你编写了用于加1的函数。但函数有个bug。你误将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))) ; Error here.
total))
当传递4给这个函数时:
(triangle-bugged 4)
在Emacs 21中,将产生一个*Backtrace*缓冲区,并进入这个缓冲区:
---------- Buffer: *Backtrace* ----------
Debugger entered--Lisp error: (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-1(nil)
eval-last-sexp(nil)
call-interactively(eval-last-sexp)
---------- Buffer: *Backtrace* ----------
(重新格式化了一下;调试不会自动折行。可以用q退出调试器)

实际上,像这样简单的bug,'Lisp error'这行告诉了我们如何修改定义。函数1=为'void'。

在Emacs 20中,你将看到:

Symbol's function definition is void: 1=
这与21版中的*Backtrace*缓冲区中的意思是一样的。

假设你还不是很清楚要如何做?你可以阅读完整的回溯信息。

在GNU Emacs 21中,它将自动启动调试器,并将信息放到*Backtrace*缓冲区中;如果有使用Emacs21,可能需要按下面的方法手工启动调试器。

在*Backtrace*中从下向上读;它说明了Emacs是如何出错的。Emacs执行了一个交互式命令C-x C-e(eval-last-sexp),它执行了triangle-bugged语句。上面的每一行显示了Lisp解释器执行内容。

缓冲区的顶部是:

(setq number (1= number))

Emacs试图执行这个语句;依次来执行,它首先执行内部的语句:

(1= number)

这里发生了错误,如错误信息所说:

Debugger entered--Lisp error: (void-function 1=)

你可以修正这个错误,然后重新执行函数定义,再运行测试代码。

debug-on-entry

GNU Emacs 21在函数出错时自动启动了调试器。GNU Emacs 20不会这样做;它只显示一条出错信息。你需要手工启动调试器。

手工启动的好处是在程序没有bug的时候也可以调试。

你可以调用debug-on-entry函数进入调试器。

输入:

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

然后,执行下面的语句:

(triangle-bugged 5)
所有版本的Emacs都将产生一个*Backtrace*缓冲区告诉你它将执行triangle-debugged函数:
---------- Buffer: *Backtrace* ----------
Debugger entered--entering a function:
* triangle-bugged(5)
eval((triangle-bugged 5))
eval-last-sexp-1(nil)
eval-last-sexp(nil)
call-interactively(eval-last-sexp)
---------- Buffer: *Backtrace* ----------
在*Backtrace*缓冲区中输入d。Emacs将执行triangle-bugged的第一行语句;缓冲区看起来如下:
---------- Buffer: *Backtrace* ----------
Debugger entered--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-1(nil)
eval-last-sexp(nil)
call-interactively(eval-last-sexp)
---------- Buffer: *Backtrace* ----------

现在,再次输入d,连续8次慢慢的输入d,Emacs将执行函数定义的另一个语句。

最后缓冲区看起来如下:

---------- Buffer: *Backtrace* ----------
Debugger entered--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-1(nil)
eval-last-sexp(nil)
call-interactively(eval-last-sexp)
---------- Buffer: *Backtrace* ----------
最后再输入两次d,Emacs将到达错误的位置,*Backtrace*缓冲区顶部的两行将显示:
---------- Buffer: *Backtrace* ----------
Debugger entered--Lisp error: (void-function 1=)
* (1= number)
...
---------- Buffer: *Backtrace* ----------

输入d可以单步执行函数。

可以输入q退出*Backtrace*缓冲区;这将退出跟踪,但并不会退出debug-on-entry。

要退出debug-on-entry,需要调用cancel-debug-on-entry并输入函数名称:

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

debug-on-quit和(debug)

除了debug-on-error或调用debug-on-entry,还有另外两种方法启动debug。

可以通过将变量debug-on-quit设置为t,随时输入C-g(keyboard-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) ; Start debugger.
(setq number (1= number))) ; Error here.
total))

edebug源码级的调试器

Edebug是一个源码级的调试器。Edebug通常显示你要调试的源码,并在左边用箭头指出当前执行的行。

你可以单步执行函数,或者快速的执行到断点位置。

下面是tringle-recursively的调试函数:

(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))))) ; Error here.
同样,你可以在函数定义后面使用C-x C-e(eval-last-sexp)安装函数,或者将光标放到定义的内部输入C-M-x(eval-defun)。(缺省情况下,eval-defun命令只在Emacs Lisp或Lisp交互模式下才可以工作。)

但是,为了使用Edebug调试函数,你必须使用另一个命令。可以将停留在函数内部然后输入

M-x edebug-defun RET

这将使Emacs自动加载Edebug。在加载完成后,可以将光标放在下面语句的后面输入C-x C-e(eval-last-sexp):

(triangle-recursively-bugged 3)
将跳到triangle-recursively-bugged的源码,光标被设置在函数if语句所在的开始行。并且,可以在这行的左边看到一个箭头。箭头标明了函数当前执行的位置。(在例子中,我们使用=>代替;在窗口系统中,你可以看到一个实心的三角形)
=>-!-(if (= number 1)
在这里,point的位置显示为-!-。

如果你输入<spc>,point将移到下一个语句;这行将显示如下:</spc>

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

如果继续输入<spc>,point将继续从一个语句移到另一个语句。每次只要语句返回了值,它都会显示到回显区。比如,在point移过number时,你将看到:</spc>

Result: 3 = C-c
这表示number的值为3,它的ASCII值是'control-c'。

你可以继续执行,直到错误的位置。在执行之前,这行如下:

=>        -!-(1= number)))))               ; Error here.

当再次输入<spc>时,将产生错误信息:</spc>

Symbol's function definition is void: 1=

输入q退出Edebug。

要从函数上移除调试的机制,可以重新使用C-x C-e执行函数定义。

Edebug除了跟踪执行外可以做更多的工作。你可以设置它在遇到错误时停止;可以让它显示或修改变量的值;你可以查找出函数被执行了多少次,等等。

评论

相关推荐

Global site tag (gtag.js) - Google Analytics