[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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] | [ ? ] |
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] | [ ? ] |
To register code with GDB, the JIT should follow this protocol:
action_flag
to JIT_REGISTER
and call
__jit_debug_register_code
.
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] | [ ? ] |
If code is freed, then the JIT should use the following protocol:
relevant_entry
field of the descriptor at the code entry.
action_flag
to JIT_UNREGISTER
and call
__jit_debug_register_code
.
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] | [ ? ] |
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] | [ ? ] |
Readers can be loaded and unloaded using the jit-reader-load
and jit-reader-unload
commands.
jit-reader-load reader-name
jit-reader-load
and then
invoking jit-reader-load
.
jit-reader-unload
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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] | [ ? ] |