《elisp 每日一练》——调试

《elisp 每日一练》——调试

Masimaro
2026-10-29 / 0 评论 / 1 阅读 / 正在检测是否收录...

前面我们一直是通过message 打印信息,以及通过出错的调用栈来看问题在哪,这种对于一些简短的代码来说,人肉编译可能更快一些,但是一旦代码规模扩大,引入复杂的第三方包,可能就不那么直观了。而且有些elisp 函数返回的数据结构比较复杂,虽然文档有说明但是没有直观的看到具体数据,在使用它获取数据时仍然显得有点复杂。这个时候就可以祭出调试这个手段了。

背景知识

elisp 的调试目前没有好用的ide,可以让我们直接在某一处快速下断点。但是仍然有几种方式下断点进入调试状态。

第一种方式是通过 edebug-defun 下一个函数断点,具体的操作是,我们要将光标放在被调试函数体的任意位置,然后执行 edebug-mode。只要函数被调用就会进入调试状态,此时会在当前buffer中启用一个 edebug-mode 的 minor mode。 我们可以通过下列快捷键进行调试:

  • <SPC> 进入下一行
  • i 进入被调函数的内部
  • c 继续运行到下一个断点
  • b 在光标所在位置下一个断点
  • x 可以插入一个条件,只有条件为真时才启用断点
  • q 退出调试模式
  • e 对表达式或者变量进行求值,可以查看变量的值

它的原理是在函数各个可能的位置进行插桩,插入调试钩子。代码运行到此处时就会触发钩子进入调试状态。

后面如果修改了问题,不想再进入该函数调试,需要将光标放到函数内部,执行 eval-defun 取消之前的插桩

edebug-defun 只能用来调试函数内部的代码,一旦出了函数就无法继续调试了,并且也看不到调用栈。因为它插入的调试钩子只在函数内部,如果想看到调用栈可以使用 debug-on-entry。

此时我们操作的是弹出来的调用栈面板。可以在面板中按 d 进行单步求值。它没有 edebug-defun 那么直观的显示当前执行到了哪行代码,我们只能根据源码和面板的输出来判断。所以它只适合用来判断函数是否被调用,以及调用的栈是怎样的。不适合用来调试具体的函数代码。

通过 debug-on-entry 下的断点可以使用 cancel-debug-on-entry 来取消

如果你不想进行调试,而是希望打印目标函数每一次被调用时传入的参数以及返回的结果, 不进入调试, 可以使用 trace-function. 我们通过一个具体的例子来看它的输出

(defun my-complex-data-processor (raw-list)
  "一个用于演示 调试的数据处理函数。"
  (let ((processed '()))
    (dolist (item raw-list)
      (let ((transformed (if (numberp item)
                             (* item 2)
                           (upcase item))))
            (push transformed processed)))
    (nreverse processed)))

(let* ((arg (list 10 "hello world" ?=))
       (ret (my-complex-data-processor arg)))
  (setq ret (my-complex-data-processor arg))
  (message "%s" ret))

trace-function 会输出如下内容:

1 -> (my-complex-data-processor (10 "hello world" 61))
1 <- my-complex-data-processor: (20 "HELLO WORLD" 122)
======================================================================
1 -> (my-complex-data-processor (10 "hello world" 61))
1 <- my-complex-data-processor: (20 "HELLO WORLD" 122)
  • 前面的1 表示调用的深度,此时它被顶层代码调用,所以深度是1;
  • -> 和 <-表示方向,前者表示调用函数,后者表示从函数出来

第一行表示, 从顶层调用函数,参数为 (10 "hello world" 61),它对应着上面的let赋值
第二行表示, 函数返回,返回值为 (20 "HELLO WORLD" 122)
第三行表示, 第二次函数调用,此时参数为 (10 "hello world" 61), 它对应着后面的 setq
第四行表示, 第二次调用的函数返回,返回值为 (20 "HELLO WORLD" 122)

取消插桩我们可以使用 untrace-function

除了这几种方式,我们还可以手动在对应代码位置插入 (debug) ,这样代码会在断点处断下来,然后弹出调用栈的窗口,它的效果就与 debug-on-entry 类似

总结

本篇我们介绍了几种elisp的调试方法,主要有以下几种:

  • edebug-defun 调试函数, 不用时需要使用 eval-defun 解除
  • debug-on-entry 会在函数被调用时弹出调用栈,不用时需要使用 cancel-debug-on-entry 解除
  • trace-function 打印对于函数调用的信息,不用时需要使用 untrace-function 解除
0

评论 (0)

取消