[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

29. JIT Compilation Interface

This chapter documents GDB's just-in-time (JIT) compilation interface. A JIT compiler is a program or library that generates native executable code at runtime and executes it, usually in order to achieve good performance while maintaining platform independence.

Programs that use JIT compilation are normally difficult to debug because portions of their code are generated at runtime, instead of being loaded from object files, which is where GDB normally finds the program's symbols and debug information. In order to debug programs that use JIT compilation, GDB has an interface that allows the program to register in-memory symbol files with GDB at runtime.

If you are using GDB to debug a program that uses this interface, then it should work transparently so long as you have not stripped the binary. If you are developing a JIT compiler, then the interface is documented in the rest of this chapter. At this time, the only known client of this interface is the LLVM JIT.

Broadly speaking, the JIT interface mirrors the dynamic loader interface. The JIT compiler communicates with GDB by writing data into a global variable and calling a fuction at a well-known symbol. When GDB attaches, it reads a linked list of symbol files from the global variable to find existing code, and puts a breakpoint in the function so that it can find out about additional code.

29.1 JIT Declarations  Relevant C struct declarations
29.2 Registering Code  Steps to register code
29.3 Unregistering Code  Steps to unregister code
29.4 Custom Debug Info  Emit debug information in a custom format


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

29.1 JIT Declarations

These are the relevant struct declarations that a C program should include to implement the interface:

 
typedef enum
{
  JIT_NOACTION = 0,
  JIT_REGISTER_FN,
  JIT_UNREGISTER_FN
} jit_actions_t;

struct jit_code_entry
{
  struct jit_code_entry *next_entry;
  struct jit_code_entry *prev_entry;
  const char *symfile_addr;
  uint64_t symfile_size;
};

struct jit_descriptor
{
  uint32_t version;
  /* This type should be jit_actions_t, but we use uint32_t
     to be explicit about the bitwidth.  */
  uint32_t action_flag;
  struct jit_code_entry *relevant_entry;
  struct jit_code_entry *first_entry;
};

/* GDB puts a breakpoint in this function.  */
void __attribute__((noinline)) __jit_debug_register_code() { };

/* Make sure to specify the version statically, because the
   debugger may check the version before we can set it.  */
struct jit_descriptor __jit_debug_descriptor = { 1, 0, 0, 0 };

If the JIT is multi-threaded, then it is important that the JIT synchronize any modifications to this global data properly, which can easily be done by putting a global mutex around modifications to these structures.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

29.2 Registering Code

To register code with GDB, the JIT should follow this protocol:

When GDB is attached and the breakpoint fires, GDB uses the relevant_entry pointer so it doesn't have to walk the list looking for new code. However, the linked list must still be maintained in order to allow GDB to attach to a running process and still find the symbol files.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

29.3 Unregistering Code

If code is freed, then the JIT should use the following protocol:

If the JIT frees or recompiles code without unregistering it, then GDB and the JIT will leak the memory used for the associated symbol files.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

29.4 Custom Debug Info

Generating debug information in platform-native file formats (like ELF or COFF) may be an overkill for JIT compilers; especially if all the debug info is used for is displaying a meaningful backtrace. The issue can be resolved by having the JIT writers decide on a debug info format and also provide a reader that parses the debug info generated by the JIT compiler. This section gives a brief overview on writing such a parser. More specific details can be found in the source file `gdb/jit-reader.in', which is also installed as a header at `includedir/gdb/jit-reader.h' for easy inclusion.

The reader is implemented as a shared object (so this functionality is not available on platforms which don't allow loading shared objects at runtime). Two GDB commands, jit-reader-load and jit-reader-unload are provided, to be used to load and unload the readers from a preconfigured directory. Once loaded, the shared object is used the parse the debug information emitted by the JIT compiler.

29.4.1 Using JIT Debug Info Readers  How to use supplied readers correctly
29.4.2 Writing JIT Debug Info Readers  Creating a debug-info reader


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

29.4.1 Using JIT Debug Info Readers

Readers can be loaded and unloaded using the jit-reader-load and jit-reader-unload commands.

jit-reader-load reader-name
Load the JIT reader named reader-name. On a UNIX system, this will usually load `libdir/gdb/reader-name', where libdir is the system library directory, usually `/usr/local/lib'. Only one reader can be active at a time; trying to load a second reader when one is already loaded will result in GDB reporting an error. A new JIT reader can be loaded by first unloading the current one using jit-reader-load and then invoking jit-reader-load.

jit-reader-unload
Unload the currently loaded JIT reader.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

29.4.2 Writing JIT Debug Info Readers

As mentioned, a reader is essentially a shared object conforming to a certain ABI. This ABI is described in `jit-reader.h'.

`jit-reader.h' defines the structures, macros and functions required to write a reader. It is installed (along with GDB), in `includedir/gdb' where includedir is the system include directory.

Readers need to be released under a GPL compatible license. A reader can be declared as released under such a license by placing the macro GDB_DECLARE_GPL_COMPATIBLE_READER in a source file.

The entry point for readers is the symbol gdb_init_reader, which is expected to be a function with the prototype

 
extern struct gdb_reader_funcs *gdb_init_reader (void);

struct gdb_reader_funcs contains a set of pointers to callback functions. These functions are executed to read the debug info generated by the JIT compiler (read), to unwind stack frames (unwind) and to create canonical frame IDs (get_Frame_id). It also has a callback that is called when the reader is being unloaded (destroy). The struct looks like this

 
struct gdb_reader_funcs
{
  /* Must be set to GDB_READER_INTERFACE_VERSION.  */
  int reader_version;

  /* For use by the reader.  */
  void *priv_data;

  gdb_read_debug_info *read;
  gdb_unwind_frame *unwind;
  gdb_get_frame_id *get_frame_id;
  gdb_destroy_reader *destroy;
};

The callbacks are provided with another set of callbacks by GDB to do their job. For read, these callbacks are passed in a struct gdb_symbol_callbacks and for unwind and get_frame_id, in a struct gdb_unwind_callbacks. struct gdb_symbol_callbacks has callbacks to create new object files and new symbol tables inside those object files. struct gdb_unwind_callbacks has callbacks to read registers off the current frame and to write out the values of the registers in the previous frame. Both have a callback (target_read) to read bytes off the target's address space.


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by GNAT Mailserver on May, 10 2012 using texi2html