2009-07-28 Reid Kleckner Add interface for JIT code generation. * NEWS: Announce JIT interface. * doc/gdb.texinfo: Add chapter on JIT interface. * Makefile.in (SFILES): Add jit.c. (HFILES_NO_SRCDIR): Add jit.h. (COMMON_OBS): Add jit.o. * jit.c: New file. * jit.h: New file. * breakpoint.h: (enum bptype): Add bp_jit_event to enum. * breakpoint.c: (update_breakpoints_after_exec): Delete jit breakpoints after exec. (bpstat_what): Update event table for bp_jit_event. (print_it_typical): Added case for bp_jit_event. (print_one_breakpoint_location): Added case for bp_jit_event. (allocate_bp_location): Added case for bp_jit_event. (mention): Added case for bp_jit_event. (delete_command): Added case for bp_jit_event. (breakpoint_re_set_one): Added case for bp_jit_event. (breakpoint_re_set): Added call to jit_inferior_created_hook. (create_jit_event_breakpoint): New. * infrun.c (handle_inferior_event): Add handler for jit event. (follow_exec): Add call to jit_inferior_created_hook. * objfiles.c (free_objfile): Fixed a memory leak. Index: gdb/Makefile.in =================================================================== RCS file: /cvs/src/src/gdb/Makefile.in,v retrieving revision 1.1091 diff -u -r1.1091 Makefile.in --- gdb/Makefile.in 3 Jul 2009 12:06:35 -0000 1.1091 +++ gdb/Makefile.in 28 Jul 2009 21:32:02 -0000 @@ -677,7 +677,8 @@ wrapper.c \ xml-tdesc.c xml-support.c \ inferior.c gdb_usleep.c \ - record.c + record.c \ + jit.c LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c @@ -746,7 +747,7 @@ annotate.h sim-regno.h dictionary.h dfp.h main.h frame-unwind.h \ remote-fileio.h i386-linux-tdep.h vax-tdep.h objc-lang.h \ sentinel-frame.h bcache.h symfile.h windows-tdep.h linux-tdep.h \ -gdb_usleep.h +gdb_usleep.h jit.h # Header files that already have srcdir in them, or which are in objdir. @@ -828,7 +829,8 @@ solib.o solib-null.o \ prologue-value.o memory-map.o xml-support.o \ target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \ - inferior.o osdata.o gdb_usleep.o record.o + inferior.o osdata.o gdb_usleep.o record.o \ + jit.o TSOBS = inflow.o Index: gdb/NEWS =================================================================== RCS file: /cvs/src/src/gdb/NEWS,v retrieving revision 1.319 diff -u -r1.319 NEWS --- gdb/NEWS 20 Jul 2009 18:51:41 -0000 1.319 +++ gdb/NEWS 28 Jul 2009 21:32:02 -0000 @@ -3,6 +3,12 @@ *** Changes since GDB 6.8 +* GDB now has an interface for JIT compilation. Applications that +dynamically generate code can create symbol files in memory and register +them with GDB. For users, the feature should work transparently, and +for JIT developers, the interface is documented in the GDB manual in the +"JIT Compilation Interface" chapter. + * Tracepoints may now be conditional. The syntax is as for breakpoints; either an "if" clause appended to the "trace" command, or the "condition" command is available. GDB sends the condition to Index: gdb/breakpoint.c =================================================================== RCS file: /cvs/src/src/gdb/breakpoint.c,v retrieving revision 1.413 diff -u -r1.413 breakpoint.c --- gdb/breakpoint.c 7 Jul 2009 22:21:09 -0000 1.413 +++ gdb/breakpoint.c 28 Jul 2009 21:32:02 -0000 @@ -59,6 +59,7 @@ #include "top.h" #include "wrapper.h" #include "valprint.h" +#include "jit.h" /* readline include files */ #include "readline/readline.h" @@ -1575,6 +1576,13 @@ continue; } + /* JIT breakpoints must be explicitly reset after an exec(). */ + if (b->type == bp_jit_event) + { + delete_breakpoint (b); + continue; + } + /* Thread event breakpoints must be set anew after an exec(), as must overlay event and longjmp master breakpoints. */ if (b->type == bp_thread_event || b->type == bp_overlay_event @@ -2573,6 +2581,7 @@ case bp_watchpoint_scope: case bp_call_dummy: case bp_tracepoint: + case bp_jit_event: default: result = PRINT_UNKNOWN; break; @@ -3298,6 +3307,9 @@ /* We hit the shared library event breakpoint. */ shlib_event, + /* We hit the jit event breakpoint. */ + jit_event, + /* This is just used to count how many enums there are. */ class_last }; @@ -3313,6 +3325,7 @@ #define clr BPSTAT_WHAT_CLEAR_LONGJMP_RESUME #define sr BPSTAT_WHAT_STEP_RESUME #define shl BPSTAT_WHAT_CHECK_SHLIBS +#define jit BPSTAT_WHAT_CHECK_JIT /* "Can't happen." Might want to print an error message. abort() is not out of the question, but chances are GDB is just @@ -3333,12 +3346,13 @@ back and decide something of a lower priority is better. The ordering is: - kc < clr sgl shl slr sn sr ss - sgl < shl slr sn sr ss - slr < err shl sn sr ss - clr < err shl sn sr ss - ss < shl sn sr - sn < shl sr + kc < jit clr sgl shl slr sn sr ss + sgl < jit shl slr sn sr ss + slr < jit err shl sn sr ss + clr < jit err shl sn sr ss + ss < jit shl sn sr + sn < jit shl sr + jit < shl sr shl < sr sr < @@ -3356,28 +3370,18 @@ table[(int) class_last][(int) BPSTAT_WHAT_LAST] = { /* old action */ - /* kc ss sn sgl slr clr sr shl - */ -/*no_effect */ - {kc, ss, sn, sgl, slr, clr, sr, shl}, -/*wp_silent */ - {ss, ss, sn, ss, ss, ss, sr, shl}, -/*wp_noisy */ - {sn, sn, sn, sn, sn, sn, sr, shl}, -/*bp_nostop */ - {sgl, ss, sn, sgl, slr, slr, sr, shl}, -/*bp_silent */ - {ss, ss, sn, ss, ss, ss, sr, shl}, -/*bp_noisy */ - {sn, sn, sn, sn, sn, sn, sr, shl}, -/*long_jump */ - {slr, ss, sn, slr, slr, err, sr, shl}, -/*long_resume */ - {clr, ss, sn, err, err, err, sr, shl}, -/*step_resume */ - {sr, sr, sr, sr, sr, sr, sr, sr}, -/*shlib */ - {shl, shl, shl, shl, shl, shl, sr, shl} + /* kc ss sn sgl slr clr sr shl jit */ +/* no_effect */ {kc, ss, sn, sgl, slr, clr, sr, shl, jit}, +/* wp_silent */ {ss, ss, sn, ss, ss, ss, sr, shl, jit}, +/* wp_noisy */ {sn, sn, sn, sn, sn, sn, sr, shl, jit}, +/* bp_nostop */ {sgl, ss, sn, sgl, slr, slr, sr, shl, jit}, +/* bp_silent */ {ss, ss, sn, ss, ss, ss, sr, shl, jit}, +/* bp_noisy */ {sn, sn, sn, sn, sn, sn, sr, shl, jit}, +/* long_jump */ {slr, ss, sn, slr, slr, err, sr, shl, jit}, +/* long_resume */ {clr, ss, sn, err, err, err, sr, shl, jit}, +/* step_resume */ {sr, sr, sr, sr, sr, sr, sr, sr, sr }, +/* shlib */ {shl, shl, shl, shl, shl, shl, sr, shl, shl}, +/* jit_event */ {jit, jit, jit, jit, jit, jit, sr, jit, jit} }; #undef kc @@ -3390,6 +3394,7 @@ #undef sr #undef ts #undef shl +#undef jit enum bpstat_what_main_action current_action = BPSTAT_WHAT_KEEP_CHECKING; struct bpstat_what retval; @@ -3460,6 +3465,9 @@ case bp_shlib_event: bs_class = shlib_event; break; + case bp_jit_event: + bs_class = jit_event; + break; case bp_thread_event: case bp_overlay_event: case bp_longjmp_master: @@ -3593,6 +3601,7 @@ {bp_longjmp_master, "longjmp master"}, {bp_catchpoint, "catchpoint"}, {bp_tracepoint, "tracepoint"}, + {bp_jit_event, "jit events"}, }; static char bpenables[] = "nynny"; @@ -3721,6 +3730,7 @@ case bp_overlay_event: case bp_longjmp_master: case bp_tracepoint: + case bp_jit_event: if (opts.addressprint) { annotate_field (4); @@ -4362,6 +4372,7 @@ case bp_shlib_event: case bp_thread_event: case bp_overlay_event: + case bp_jit_event: case bp_longjmp_master: loc->loc_type = bp_loc_software_breakpoint; break; @@ -4644,6 +4655,17 @@ int radix; }; +/* Create a breakpoint for JIT code registration and unregistration. */ + +struct breakpoint * +create_jit_event_breakpoint (struct gdbarch *gdbarch, CORE_ADDR address) +{ + struct breakpoint *b; + + b = create_internal_breakpoint (gdbarch, address, bp_jit_event); + update_global_location_list_nothrow (1); + return b; +} void remove_solib_event_breakpoints (void) @@ -5279,6 +5301,7 @@ case bp_shlib_event: case bp_thread_event: case bp_overlay_event: + case bp_jit_event: case bp_longjmp_master: break; } @@ -7585,6 +7608,7 @@ { if (b->type != bp_call_dummy && b->type != bp_shlib_event + && b->type != bp_jit_event && b->type != bp_thread_event && b->type != bp_overlay_event && b->type != bp_longjmp_master @@ -7604,6 +7628,7 @@ if (b->type != bp_call_dummy && b->type != bp_shlib_event && b->type != bp_thread_event + && b->type != bp_jit_event && b->type != bp_overlay_event && b->type != bp_longjmp_master && b->number >= 0) @@ -7926,6 +7951,7 @@ case bp_step_resume: case bp_longjmp: case bp_longjmp_resume: + case bp_jit_event: break; } @@ -7954,6 +7980,8 @@ set_language (save_language); input_radix = save_input_radix; + jit_inferior_created_hook (); + create_overlay_event_breakpoint ("_ovly_debug_event"); create_longjmp_master_breakpoint ("longjmp"); create_longjmp_master_breakpoint ("_longjmp"); Index: gdb/breakpoint.h =================================================================== RCS file: /cvs/src/src/gdb/breakpoint.h,v retrieving revision 1.95 diff -u -r1.95 breakpoint.h --- gdb/breakpoint.h 2 Jul 2009 17:12:24 -0000 1.95 +++ gdb/breakpoint.h 28 Jul 2009 21:32:02 -0000 @@ -120,6 +120,9 @@ bp_catchpoint, bp_tracepoint, + + /* Event for JIT compiled code generation or deletion. */ + bp_jit_event, }; /* States of enablement of breakpoint. */ @@ -548,6 +551,9 @@ keep checking. */ BPSTAT_WHAT_CHECK_SHLIBS, + /* Check for new JITed code. */ + BPSTAT_WHAT_CHECK_JIT, + /* This is just used to keep track of how many enums there are. */ BPSTAT_WHAT_LAST }; @@ -841,6 +847,9 @@ extern void make_breakpoint_permanent (struct breakpoint *); +extern struct breakpoint *create_jit_event_breakpoint (struct gdbarch *, + CORE_ADDR); + extern struct breakpoint *create_solib_event_breakpoint (struct gdbarch *, CORE_ADDR); Index: gdb/infrun.c =================================================================== RCS file: /cvs/src/src/gdb/infrun.c,v retrieving revision 1.402 diff -u -r1.402 infrun.c --- gdb/infrun.c 20 Jul 2009 15:05:12 -0000 1.402 +++ gdb/infrun.c 28 Jul 2009 21:32:03 -0000 @@ -50,6 +50,7 @@ #include "event-top.h" #include "record.h" #include "inline-frame.h" +#include "jit.h" /* Prototypes for local functions */ @@ -544,6 +545,8 @@ solib_create_inferior_hook (); #endif + jit_inferior_created_hook (); + /* Reinsert all breakpoints. (Those which were symbolic have been reset to the proper address in the new a.out, thanks to symbol_file_command...) */ @@ -3529,6 +3532,22 @@ } break; + case BPSTAT_WHAT_CHECK_JIT: + if (debug_infrun) + fprintf_unfiltered (gdb_stdlog, "infrun: BPSTAT_WHAT_CHECK_JIT\n"); + + /* Switch terminal for any messages produced by breakpoint_re_set. */ + target_terminal_ours_for_output (); + + jit_event_handler (); + + target_terminal_inferior (); + + /* We want to step over this breakpoint, then keep going. */ + ecs->event_thread->stepping_over_breakpoint = 1; + + break; + case BPSTAT_WHAT_LAST: /* Not a real code, but listed here to shut up gcc -Wall. */ Index: gdb/objfiles.c =================================================================== RCS file: /cvs/src/src/gdb/objfiles.c,v retrieving revision 1.87 diff -u -r1.87 objfiles.c --- gdb/objfiles.c 22 Jul 2009 19:21:31 -0000 1.87 +++ gdb/objfiles.c 28 Jul 2009 21:32:03 -0000 @@ -25,6 +25,7 @@ #include "defs.h" #include "bfd.h" /* Binary File Description */ +#include "libbfd.h" #include "symtab.h" #include "symfile.h" #include "objfiles.h" @@ -438,9 +439,19 @@ if (objfile->obfd != NULL && !(objfile->flags & OBJF_KEEPBFD)) { char *name = bfd_get_filename (objfile->obfd); + struct bfd_in_memory *bim = NULL; + /* Hack to work around the fact that BFD does not take ownership of the + memory for files allocated in memory. */ + if (objfile->obfd->flags & BFD_IN_MEMORY) + bim = (struct bfd_in_memory *) objfile->obfd->iostream; if (!bfd_close (objfile->obfd)) warning (_("cannot close \"%s\": %s"), name, bfd_errmsg (bfd_get_error ())); + if (bim != NULL) + { + xfree (bim->buffer); + xfree (bim); + } xfree (name); } Index: gdb/doc/gdb.texinfo =================================================================== RCS file: /cvs/src/src/gdb/doc/gdb.texinfo,v retrieving revision 1.608 diff -u -r1.608 gdb.texinfo --- gdb/doc/gdb.texinfo 20 Jul 2009 18:51:41 -0000 1.608 +++ gdb/doc/gdb.texinfo 28 Jul 2009 21:32:05 -0000 @@ -159,6 +159,7 @@ * Emacs:: Using @value{GDBN} under @sc{gnu} Emacs * GDB/MI:: @value{GDBN}'s Machine Interface. * Annotations:: @value{GDBN}'s annotation interface. +* JIT Interface:: Using the JIT debugging interface. * GDB Bugs:: Reporting bugs in @value{GDBN} @@ -25822,6 +25823,136 @@ followed by one or more lowercase hex digits (note that this does not depend on the language). +@node JIT Interface +@chapter JIT Compilation Interface +@cindex just-in-time compilation +@cindex JIT compilation interface + +This chapter documents @value{GDBN}'s @dfn{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 @value{GDBN} normally finds the program's symbols +and debug information. In order to debug programs that use JIT compilation, +@value{GDBN} has an interface that allows the program to register in-memory +symbol files with @value{GDBN} at runtime. + +If you are using @value{GDBN} 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 @value{GDBN} by writing data into a global +variable and calling a fuction at a well-known symbol. When @value{GDBN} +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. + +@menu +* Declarations:: Relevant C struct declarations +* Registering Code:: Steps to register code +* Unregistering Code:: Steps to unregister code +@end menu + +@node Declarations +@section JIT Declarations + +These are the relevant struct declarations that a C program should include to +implement the interface: + +@smallexample +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 @}; +@end smallexample + +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. + +@node Registering Code +@section Registering Code + +To register code with @value{GDBN}, the JIT should follow this protocol: + +@itemize @bullet +@item +Generate an object file in memory with symbols and other desired debug +information. The file must include the virtual addresses of the sections. + +@item +Create a code entry for the file, which gives the start and size of the symbol +file. + +@item +Add it to the linked list in the JIT descriptor. + +@item +Point the relevant_entry field of the descriptor at the entry. + +@item +Set @code{action_flag} to @code{JIT_REGISTER} and call +@code{__jit_debug_register_code}. +@end itemize + +When @value{GDBN} is attached and the breakpoint fires, @value{GDBN} uses the +@code{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 +@value{GDBN} to attach to a running process and still find the symbol files. + +@node Unregistering Code +@section Unregistering Code + +If code is freed, then the JIT should use the following protocol: + +@itemize @bullet +@item +Remove the code entry corresponding to the code from the linked list. + +@item +Point the @code{relevant_entry} field of the descriptor at the code entry. + +@item +Set @code{action_flag} to @code{JIT_UNREGISTER} and call +@code{__jit_debug_register_code}. +@end itemize + +If the JIT frees or recompiles code without unregistering it, then @value{GDBN} +and the JIT will leak the memory used for the associated symbol files. + @node GDB Bugs @chapter Reporting Bugs in @value{GDBN} @cindex bugs in @value{GDBN}