Emacs 常用 debug 手段


折腾 Emacs 难免会遇到问题。

论坛里经常可以看到有同学遇到各种配置问题的,但却不知从何入手排查。这里我给大家简单地介绍一些常用的方法,当你下次遇到问题的时候可以先照着排查一下,说不定就能自己解决了。

emacs -Q 启动不加载任何配置的 Emacs

基本每个人都会有自己的配置,在遇到各种 Emacs 内置特性相关问题的时候,如果别人没有问题,那么很可能就是自己的配置引起的。这时可以启用一个干净的环境来验证排查。

具体方法是:

  1. Linux/macOS 中打开终端,在终端中执行命令 emacs -Q 就可以了。

    /img/2022-07-17-emacs-q-linux.png
    Linux 终端用 emacs -Q 启动 Emacs

    如果你比较习惯终端界面,或者 Emacs 运行在服务器上,那么可以加一个 -nw 参数,即命令 emacs -Q -nw

    P.S. 如果你想了解更多的命令行参数,可以通过查看 man 手册(命令 man emacs )或者查看 emacs help 信息(执行命令 emacs --help ),增进对 Emacs 命令行选项的了解。

  2. Windows 上打开 cmd ,进入 Emacs bin 目录,然后执行 runemacs.exe -Q

    /img/2022-07-17-emacs-q-windows.png
    Windows cmd 用 emacs -Q 启动 Emacs

在干净的 Emacs 中加载故障包

通过 emacs -Q 排查 Emacs 内置的特性问题非常有帮助。但另一方面,我们也常常会从 melpa 等仓库安装一些包,而这一块更经常出问题。

那么对于第三方的包如何启动一个干净的 Emacs 来验证呢?只要在 emacs -Q 的基础上增加点东西就可以了,大致有两种方法。

package 加载包

例如现在我们要调查 ppcompile 包的问题,那么可以通过这么一段小代码(假如放在文件 /tmp/try-ppcompile.el~ 中)来准备一个干净的环境:

;; 如果你没有手动更改过 package 的包目录,下边这行可以不用
(setq package-user-dir "~/.emacs.local.d/elpa")

;; 初始化 package 包管理器
(require 'package)
(package-initialize)

;; 加载你想要 debug 的包
(require 'ppcompile)

然后在终端中执行命令 emacs -Q -l /tmp/try-ppcompile.el ,这样就在一个干净的 Emacs 环境中“安装”好了 ppcompile 包了。一般来说,目标包已经在 package-user-dir 目录下了,这里并不需要再次下载包, Emacs 会直接加载这个包,因此能够秒开。

直接从源码目录加载包

如果你用某个包的最新版本,而它在包仓库中还没有更新(比如你用 git submodule 来锁定包版本),那么你也可以利用 Emacs -L 启动参数从本地目录来加载那个包。注意你可以指定多个目录,如果必要的话。

假设你的 git clone 了包在 /tmp/ppcompile/ 目录下,那么可以这样来加载包及调试: emacs -Q -L /tmp/ppcompile/ --eval "(require 'ppcompile)"

(感谢 @wzhe 补充这个方法。)

通过 --debug-init 排查启动 Emacs 时就报错的问题

当你更新了配置之后,可能会出现 Emacs 启动的时候直接报错了,比如类似下边这样:

/img/2022-08-07-emacs-startup-error.png
Emacs 启动报错

不要着急,其实 Emacs 已经告诉你怎么解决了(注意截图中的第二个红线处)。把当前的 Emacs 关了,打开终端,执行命令 emacs --debug-init 另起一个。她会告诉你报错的位置和调用堆栈,然后顺藤摸瓜,查看对应的文件代码来理解问题原因,再想办法解决就行(截图演示的错误是因为 a-bug-function 这个函数并未定义)。

/img/2022-08-07-emacs-debug-init.png
加 –debug-init 参数启动 Emacs

使用 M-x toggle-debug-on-error 命令打开 debug 开关

(假如你还不知道, M-x toggle-debug-on-error 对应的按键是 ALT+x toggle-debug-on-error 回车

有时候,在使用命令或者按键的时候,可能在底部的 echo area 会看到报错:

/img/2022-07-17-symbols-function-definition-is-void.png
echo area 提示找不到函数定义

比如有这么一条 welcome 命令:

(defun welcome ()
  (interactive)
  (this-func-not-exist))

当你用 M-x welcome 发起命令的时候,就会看到有上述的报错。这时候执行 M-x toggle-debug-on-error 打开调试开关之后,再执行就会弹出出错时的调用栈。

/img/2022-07-17-elisp-debugger-backtrace.png
elisp debugger backtrace 1: 函数未定义

从最上面的部分可以看出来,这个报错是因为 welcome 命令想要调用 this-func-not-exist ,但后者却没有定义,当然就出错啦。

设置 debug-on-message 变量

还有一种情况,你可能只在底部看到有某种 message 但没有报错,但是它的行为与预期不符。此时可以通过设置 debug-on-message 才缩小关注的代码范围,再尝试找到背后的原因。不过,此方法需要对 elisp 具有一定的了解。

比如,调整一下上述的命令实现,让它输出一条讯息 welcome who?

(defun welcome ()
  (interactive)
  (message "welcome who?"))

假设我们现在需要找出到底在哪里输出了 welcome who 这一段莫名其妙的讯息(一般适用于功能比较复杂、调用栈较深的命令),那么就可以利用 debug-on-message 变量设置一个正则表达式 regexp : (setq debug-on-message "welcome who")

一旦有人输出, Emacs 就会陷入 debugger ,我们就可以看到在哪里输出了对应的 message :

/img/2022-07-17-elisp-debugger-backtrace.png
elisp debugger backtrace 2: 命中 message 模式

其他 Emacs 内置的排查问题方法

查看各种帮助的文档:

  • C-h f 查看函数( C-h f 表示的按键是 CTRL+h 再按 f 键
  • C-h v 查看变量
  • C-h k 查看某个按键绑定了什么命令
  • C-h m 查看当前 buffer 开启了哪些 major/minor modes ,其中也会汇总它定义了哪些按键
  • C-h l 查看按键历史
  • M-x describe-char 查看光标下的字符相关的属性,比如使用的字体、 face (找到之后可以用于定制颜色、主题等)
  • debug-on-entry 函数,可以设置当某个函数被调用时进入 debugger debug-on-entry (Programming in Emacs Lisp) - www.gnu.org
  • debug-on-quit 变量 debug-on-quit (Programming in Emacs Lisp) - www.gnu.org
  • --init-directory 指定配置目录 Emacs 29 增加了 --init-directory 来指定配置目录,这样就可以很方便地起一个单独的环境来测试验证特定的配置

也可以看看

comments powered by Disqus