前面我们一直是通过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)