Debugging dynamically generated code in Emacs GUD
At Twitter, I’m going to work on the Hotspot JVM, so I thought I’d start with that. Between Debian’s apt-get build-dep to install build dependencies and the exhaustive list of steps in the official wiki, it turned out to be really easy to build the OpenJDK from source. I was up and running with a debug build in a few minutes. Next, the debugger - GDB. I typically use a debugger from within Emacs, using one of my favorite major modes - the Grand Unified Debugger (or GUD) mode. GUD is an Emacs based user interface for command line debuggers. On Windows, it wraps CDB (cdb-gud.el; CDB is the command line version of the Windows systems debugger - Windbg) and on Linux, GDB.
This is where I ran into a small problem. GUD-MI’s (MI or the Machine Interface being the line based, machine oriented text interface to GDB. GUD uses it instead of trying to parse GDB’s output) disassembly buffer only works when the frame being debugged has associated debug information. While debugging dynamically generated code, it is useful to be able to simply follow the program counter. My first instinct was to implement debuginfo support in Hotspot using the GDBJIT API that recent versions of GDB support. (GDBJIT is GDB’s just-in-time compilation interface. If a JIT implements the GDBJIT interface, GDB, instead of looking for debug information in object files, will talk to the JIT instead to obtain it). That however would only solve half the problem as there are often small code sequences - stubs, thunks etc., for which we can’t. Thus, having the disassembly buffer follow $PC would still be useful while stepping through dynamically generated, shorter sequences of code (like call stubs, inline caches etc.). A quick check revealed that the latest GDB (22.214.171.12420122-cvs) follows the program counter in the absence of debug information, in its TUI (C-x-o) mode but my stable GDB’s (7.0.1-debian) behavior was the same as Emacs 23 GUD, in that it didn’t.
I knew that the fix would be trivial if this was just GUD behaving differently. GUD, like other major modes is implemented in ELisp. When navigating an unfamiliar, fairly large piece of ELisp, the built-in Emacs Lisp debugger is your friend. It can suspend the Lisp evaluator and examine and/or alter application state. It took me a bit to trace through the implementation to find the piece of code that handled stepping through assembly, but once I did, the fix, as I’d suspected was trivial.
Here’s the patch I came up with:
=== modified file 'lisp/progmodes/gdb-mi.el'
--- lisp/progmodes/gdb-mi.el 2012-01-05 09:46:05 +0000
+++ lisp/progmodes/gdb-mi.el 2012-01-24 05:13:10 +0000
@@ -3259,8 +3259,12 @@
(let* ((frame (gdb-current-buffer-frame))
(file (bindat-get-field frame 'fullname))
(line (bindat-get-field frame 'line)))
- (when file
- (format "-data-disassemble -f %s -l %s -n -1 -- 0" file line)))
+ (if file
+ (format "-data-disassemble -f %s -l %s -n -1 -- 0" file line)
+ ;; If we're unable to get a file name / line for $PC, simply
+ ;; follow $PC, disassembling the next 10 (x ~15 (on IA) ==
+ ;; 150 bytes) instructions.
+ "-data-disassemble -s $pc -e \"$pc + 150\" -- 0"))
;; We update disassembly only after we have actual frame information
;; about all threads, so no there's `update' signal in this list
@@ -3319,8 +3323,12 @@
(bindat-get-field instr 'address)
- (apply #'format "<%s+%s>:"
- (gdb-get-many-fields instr 'func-name
+ ((func-name (bindat-get-field instr 'func-name))
+ (offset (bindat-get-field instr 'offset)))
+ (if func-name
+ (format "<%s+%s>:" func-name offset)
(bindat-get-field instr 'inst)))
(when (string-equal (bindat-get-field instr 'address)
Instead of not printing an assembly listing in the absence of debug information, my fix was to print N bytes starting at $PC. I set N to a 150 bytes, which on x86 might correspond roughly, to 10 instructions. That is it. A simple enough patch to get it to work the way GDB and other debuggers do. Since this is only really useful while debugging code that doesn’t have symbols, typically dynamically generated code, I suspect this’d be useful to a lot of developers, but I’m going to try to upstream it anyway. We’ll see how that goes. If it gets in, it’d be my first contribution to Emacs.