Learning to Use the Emacs Debugger

Updates

Learning To Use the Emacs Debugger

One thing we haven’t talked about much on this channel yet is how to diagnose problems in your Emacs configuration beyond just looking at the *Messages* buffer and hoping you can figure out the cryptic error message that appeared.

Today we’re going to take a look at Emacs’ built-in Emacs Lisp debuggers, debug and edebug (yes, there are two!). The goal is to figure out some good debugging strategies, not only for your personal configuration but also for Emacs Lisp packages that you use or those that you authored yourself!

We’ll be using the Debugging Lisp Programs section of the Emacs Lisp manual as our resource. You can get to it inside of Emacs by evaluating the following expression with M-:

(info "(elisp) Debugging")

Features to Investigate

debug

Entering the debugger

  • M-x toggle-debug-on-error
  • M-x toggle-debug-on-quit
  • (setq debug-on-message "Oops")
  • M-x debug-on-entry / cancel-debug-on-entry
  • M-x debug-on-variable-change / cancel-debug-on-variable-change
  • (debug)

Inspecting backtraces

When the *Backtrace* buffer appears, try these bindings:

  • v -> Display local variables of stack frame
  • p -> Move to previous stack frame
  • n -> Move to next stack frame
  • + -> Add line breaks to stack trace to make it readable
  • - -> Make stack frame expression a single line
  • # -> Toggle print-circle for frame
  • : -> Toggle print-gensym for frame
  • . -> Expand all ... forms

Debugger commands

  • c -> Continue execution
  • d -> Continue execution but stop again at the next function call
  • b -> Flag the current frame so that the debugger stops again when it exits
  • u -> Undo the flag on the current frame
  • e -> Eval an expression at the current stack frame
  • r -> Return a value for the currently executing frame (useful for errors)
  • l -> List all functions that will cause the debugger to break

edebug

One difference with edebug is that you can actually step through execution of a function! The downside is that you have to explicitly instrument a function by evaluating it with C-u C-M-x or (M-x edebug-eval-top-level-form).

You can debug all definitions by setting edebug-all-defs to t before evaluating the code.

Let’s examine the Edebug manual to figure out what to try:

(info "(elisp) Edebug Execution Modes")

The Example

Here’s the example I was using in the stream:

(defvar my-variable 5)

(defun return-something ()
  (+ 4 1))

(defun do-bad-thing ()
  (let ((x 5)
        (y 0))
    (message "The result is: %s" (return-something))
    ;; (/ x y)
    ;; (cl-do () (nil) (message "Oops!"))
    ))

(advice-add 'org-cycle-item-indentation :after #'do-bad-thing)
(advice-remove 'org-cycle-item-indentation #'do-bad-thing)

(setq debug-on-message "^O")
Subscribe to the System Crafters Newsletter!
Stay up to date with the latest System Crafters news and updates! Read the Newsletter page for more information.
Name (optional)
Email Address