CLJDB 0.3

Using Emacs Jdb mode to debug Clojure

What is CLJDB?

CLJDB is a hack that adds some Clojure support to emacs jdb, (command line java debug interface,) mode. It still a work in progress, and has some funky aspects due to language differences between Clojure and Java, but you can:

all from within emacs.

Setup

Load Emacs jdb enhancements for Clojure

In your .emacs, add:

(load-file "<path>/cljdb.el")

Then evaluate that sexp, or restart emacs.

The file is located here: http://georgejahad.com/clojure/cljdb.el

Set Classpath

Emacs jdb mode assumes that all source can be found from the class path, so you need to update your classpath to include a pointer to any directories that have source code you want to debug. The directory structure must be namespace particular so, for example, to debug clojure.core, you need an entry in your classpath that points to a directory that contains clojure/core.clj.

Set namespaces

All clojure source code must contain a namespace command, e.g. "(ns clojure.core)". cljdb.el uses this to help determine the proper source path.

Set JVM command line args

The jdb will require the vm to be started with the following command line args:

-agentlib:jdwp=transport=dt_socket,address=8021,server=y,suspend=n

The address above, 8021, is an arbitrary port number and can be changed as needed.

If using slime-clojure, adding this to your .emacs suffice:

(setq swank-clojure-extra-vm-args '("-agentlib:jdwp=transport=dt_socket,address=8021,server=y,suspend=n"))

Start Emacs Jdb mode

M-x jdb -attach 8021

This will start up the jdb and connect it to the vm. It will bring up a buffer called:

*gud-8021*

This buffer displays the jdb and you can run standard jdb commands from here.

Useful commands

Keystroke commands

Keystroke Command Executed
C-x C-a C-b From within a file buffer, sets a breakpoint on the current line. (Currently only works on clj files. The rest of the commands below should work on both .java and .clj files.)
C-x C-a C-n jdb Next command
C-x C-a C-s jdb Step command
C-x C-a C-f jdb Step up command, which runs to the end of the current function
C-x C-a C-l refresh the display
C-x C-a C-r continue
C-x C-a < up stackframe
C-x C-a > down stackframe

Other useful commands that can be run from the jdb command line

Command Output
locals list of the method args and local variables
print <var> prints the contents of var, which could be a method,instance or class variable, or a method argument
where print the stacktrace, and refresh the file display
catch trap java exceptions
help print all available jdb commands
clear view/unset current breakpoints

Test Drive

To confirm that it is all working, run the following tests:

Catch exception

At the jdb command line in the*gud-8021* buffer, type:

catch all java.lang.NullPointerException

At the repl, type:

(max-key :b {:b 1} nil)

At this point a buffer should pop up containing the clojure/src/jvm/clojure/lang/Numbers.java source, with the cursor at this line:

Class xc = x.getClass();

Note that the exception was caused by trying to dereference the null pointer x. From here, you should be able to go up and down the stack with the appropriate file being displayed in the buffer.

You should also be able to use the print command from the jdb command line to print out the values of different variables. For example, in the above stack frame, this command:

 print x

should return:

 x = null

while one stack frame up, it should print:

 x = "1"

Continue on from this exception, so that you get back to the repl prompt.

Set Breakpoint

Bring up the clojure/core.clj source file in a buffer; (it needs to be the one that is pointed to by the classpath.) Set a breakpoint in the into defn on this line:

      (if items

Now from the repl run this command:

(into [1 2 3] [ 4 5 6])

When the into function comes up in the buffer, hit next a few times to step through it, using "print items" occasionally to see how the value of the collection changes.

Known Problems

This implementation is a text-book hack. I've made no effort to actually architect this mode for Clojure. Instead, I just ran jdb mode with some clojure files, saw where it was breaking, and patched those particular problems. That means there are probably other problems lurking, which I simply haven't come across yet. If cljdb becomes popular, I'll go back and do it right. Even so, I've found it remarkably useful as is. Hopefully you will too. Here's my current bug list:

Could not find source file.

Comments/Suggestions

Send any comments/suggestions to George Jahad at "george-clojure at blackbirdsystems.net" or to the main clojure mailing list: http://groups.google.com/group/clojure