New JIT Interface for GDB

I worked on GDB as a part of my internship at Igalia. The work I did is finally merged, and this blog post describes the problem that I tried to solve and the solution I implemented.

The Problem

Let’s say A is a runtime function that sometimes gets called by JIT compiled code. When the control is inside A, the stack may look something like this:

[  A  ]
[ Some JIT compiled function (X)  ]
[ Some other JIT compiled function (Y)  ]
[ Some function that launches JIT compiled code (B)  ]
[ Some function that does the JIT compilation (C)  ]
[   .... and so on ....  ]
[  First function for this thread == (D)  ]

Now, for GDB to be able to successfully unwind through the X and Y frames, it needs some extra information. For regular functions (like A, B, C and D) this information is embedded in a section in the ELF file, using the DWARF encoding. However, X and Y don’t have any such associated information since they were generated on the fly. On trying to backtrace when the stack is in the state shown above you’ll

  • See a bunch of ??s instead of X and Y, since GDB does not have the debug symbols.
  • A possibly incorrect backtrace. From what I understand, GDB can backtrace by interpreting the prologue, but only up to a point.

The initial solution (still supported by GDB) was this: whenever the JIT compiler compiles a block of code, have it emit the debug information to an ELF file in memory. Then have the JIT compiler notify GDB of these in-memory ELF files using a published interface. This way, GDB knows how to display a pretty backtrace.

This solution (of generating ELF files in memory) is, however, not ideal. ELF files are complex and hard to generate. Generating ELF files bites even more if all that GDB needs to do is display a meaningful stack trace. My job was to find a less complex way to get the debugging information into GDB.

The Solution

It was Tom Tromey’s idea that it probably makes more sense to load and use some sort of a plugin provided by the JIT vendors. I too thought this idea was viable and decided to implement it and see how well it worked.

The problem can be divided into two parts — providing the debug symbols and providing the unwind information. We don’t deal with providing type information and other such more sophisticated information, since we’re targeting people who just want to see a meaningful stack trace without putting in too much effort. The ELF handling code is still in GDB and anyone wishing to do something more exciting than looking at a backtrace can generate full-fledged ELF files.

For the debug symbols part, we

  1. Have the JIT compiler generate the debug symbol information in any format it pleases. GDB does not need to understand this format.
  2. Have the plugin parse this debug information (since it understands the format, being written by the JIT vendor) and emit the symbol tables GDB requires.

In the name of a clean interface and better backward compatibility, (2) is done by passing a bunch of callback functions to the plugin, along with the data it is supposed to parse (the data that the JIT compiler passed to GDB). The plugin then constructs the symbol tables using the callbacks it is given. There are callbacks to create a new symbol table (symtab_open), to add a mapping from line numbers to PCs (line_mapping_add) among others. All this is documented in the header jit-reader.h.

jit-reader.h is installed in {includdir}/gdb/, where {includedir} is the system’s default include directory. This header is the interface between the plugin and GDB, and needs to be included in the plugin source file. Because of where it is installed by default, usually a #include <gdb/jitreader.h> is sufficient.

For the unwinding part we simply have the plugin provide an unwinder. The unwinder too gets a bunch of callbacks, one to read the value of a register in the current frame, one to tell GDB (write) the value of a register in the previous frame (the one that was called immediately before), one to read some data off the target process’ address space, one to generate a frame id (described in jit-reader.h) and so on. The unwinder is especially easy to write if the JIT compiled code stores interesting things at constant offsets from the base register. Pseudo code could be

  READ_REGISTER(cb, AMD64_RBP, FramePtr);
  PreviousFP = READ_MEMORY_AT(FramePtr)
  PreviousPC = READ_MEMORY_AT(FramePtr + 8)

  WRITE_REGISTER(AMD64_RBP, PreviousFP)
  WRITE_REGISTER(AMD64_RA, PreviousPC)

Telling GDB the values of the frame pointer and the program counter is enough for it to display a backtrace.

I’ve checked in the code, and it is available in the current trunk. This was a very general bird’s eye view of the situation, more information can be found in the GDB manual and the jit-reader.h file GDB installs. Thanks to everyone on the GDB mailing list who sent in their valuable reviews.

Comments, suggestions and questions welcome.

Advertisement
This entry was posted in Computers and tagged , , , . Bookmark the permalink.

2 Responses to New JIT Interface for GDB

  1. Hi! Thank you for your effort 🙂 I appreciate your solution.

    I wanted to know if your work is now part of the GDB stable releases. In fact, I can’t figure out how to find the header “jit-reader.h” on my Debian 6.0 even though I installed most of what I saw relative to GDB. Can you help me?

    • Sanjoy says:

      I’m not sure why the jit-reader.h file hasn’t been included with the standard gdb distribution in debian. The cleanest solution is probably to build gdb from source.

      Of course, once you have a JIT reader plugin, say for gdb 7.4, you should be able to use it with any other gdb 7.4 installation; irrespective of whether that particular installation contains the header or not.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s