17.4 edebug 源码级调试器

Edebug 是一款源码级调试器。Edebug 通常会显示你正在调试的代码源码,并在左侧用箭头标出当前执行到哪一行。

你可以逐行单步跟踪函数执行,也可以让程序快速运行,直到遇到 断点(breakpoint) 时停下。

Edebug 的详细说明见 Edebug in The GNU Emacs Lisp Reference Manual

下面是 triangle-recursively 的一个带错误版本的函数定义。回顾内容可参考 See Recursion in place of a counter

(defun triangle-recursively-bugged (number)
  "返回数字 1 到 NUMBER 的累加和。
使用递归实现。"
  (if (= number 1)
      1
    (+ number
       (triangle-recursively-bugged
        (1= number)))))               ; Error here.

通常情况下,你会将光标移到函数结尾的右括号后,按 C-x C-eeval-last-sexp)加载该定义;或者将光标放在定义内部,按 C-M-xeval-defun)。(默认情况下,eval-defun 命令只在 Emacs Lisp 模式或 Lisp 交互模式下生效。)

不过,要让这个函数能被 Edebug 使用,必须先用另一个命令对代码进行 插桩(instrument)。你可以将光标放在函数定义内部或紧接其后,然后输入:

M-x edebug-defun RET

如果 Edebug 尚未加载,Emacs 会自动加载它,并对函数完成正确插桩。

函数插桩完成后,将光标放在下面表达式的后面,按 C-x C-eeval-last-sexp):

(triangle-recursively-bugged 3)

界面会跳转到 triangle-recursively-bugged 的源码处,光标会定位在函数的 if 行开头。同时,你会在该行左侧看到一个箭头符号。这个箭头标记了函数当前正在执行的行。(下面的例子中我们用 ‘=>’ 表示箭头;在图形化 框架 中,你可能会在 框架 边缘看到一个实心三角箭头。)

=>∗(if (= number 1)

本例中,光标位置显示为 ‘’(在印刷书籍中会用五角星标出)。

如果现在按 SPC,光标会移动到下一个待执行的表达式,该行会变成:

=>(if ∗(= number 1)

继续按 SPC,光标会在各个表达式之间移动。同时,每当一个表达式返回值时,该值会显示在回显区。例如,当光标移过 number 后,你会看到:

Result: 3 (#o3, #x3, ?\C-c)

意思是 number 的值为 3,对应八进制 3、十六进制 3,以及 ASCII 字符 Control-C(字母表第三个字符,仅供参考)。

你可以继续单步执行代码,直到到达出错的行。执行前,该行看起来是这样的:

=>        ∗(1= number)))))               ; Error here.

再按一次 SPC,就会出现错误提示:

Symbol's function definition is void: 1=

这就是程序中的 Bug。

q 退出 Edebug。

要移除函数的插桩,只需用不进行插桩的命令重新执行一遍定义即可。例如,将光标移到函数结尾右括号后,按 C-x C-e

Edebug 的功能远不止单步跟踪函数。你可以让它快速自动运行,只在出错或指定断点处停下;可以让它实时显示各个表达式的变化值;可以统计函数被调用的次数,等等。

Edebug 的详细说明见 Edebug in The GNU Emacs Lisp Reference Manual