diff options
-rw-r--r-- | gdb/ChangeLog | 24 | ||||
-rw-r--r-- | gdb/Makefile.in | 8 | ||||
-rw-r--r-- | gdb/NEWS | 6 | ||||
-rw-r--r-- | gdb/breakpoint.c | 84 | ||||
-rw-r--r-- | gdb/breakpoint.h | 9 | ||||
-rw-r--r-- | gdb/doc/ChangeLog | 4 | ||||
-rw-r--r-- | gdb/doc/gdb.texinfo | 131 | ||||
-rw-r--r-- | gdb/infrun.c | 19 | ||||
-rw-r--r-- | gdb/jit.c | 438 | ||||
-rw-r--r-- | gdb/jit.h | 77 |
10 files changed, 769 insertions, 31 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 1c4c56d..291c965 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,27 @@ +2009-07-24 Reid Kleckner <reid@kleckner.net> + + Add interface for JIT code generation. + * NEWS: Announce 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. + 2009-08-19 Ulrich Weigand <uweigand@de.ibm.com> * value.c (enum internalvar_kind): Replace INTERNALVAR_SCALAR by diff --git a/gdb/Makefile.in b/gdb/Makefile.in index 9c2b9c7..90c285f 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -676,7 +676,8 @@ SFILES = ada-exp.y ada-lang.c ada-typeprint.c ada-valprint.c ada-tasks.c \ 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 @@ -745,7 +746,7 @@ config/rs6000/nm-rs6000.h top.h bsd-kvm.h gdb-stabs.h reggroups.h \ 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. @@ -827,7 +828,8 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $(YYOBJ) \ 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 @@ -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 diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c index f3940e1..1f799b0 100644 --- a/gdb/breakpoint.c +++ b/gdb/breakpoint.c @@ -59,6 +59,7 @@ #include "top.h" #include "wrapper.h" #include "valprint.h" +#include "jit.h" /* readline include files */ #include "readline/readline.h" @@ -1592,6 +1593,13 @@ update_breakpoints_after_exec (void) 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 @@ -2583,6 +2591,7 @@ print_it_typical (bpstat bs) case bp_watchpoint_scope: case bp_call_dummy: case bp_tracepoint: + case bp_jit_event: default: result = PRINT_UNKNOWN; break; @@ -3308,6 +3317,9 @@ bpstat_what (bpstat bs) /* 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 }; @@ -3323,6 +3335,7 @@ bpstat_what (bpstat bs) #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 @@ -3343,12 +3356,13 @@ bpstat_what (bpstat bs) 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 < @@ -3366,28 +3380,18 @@ bpstat_what (bpstat bs) 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 @@ -3400,6 +3404,7 @@ bpstat_what (bpstat bs) #undef sr #undef ts #undef shl +#undef jit enum bpstat_what_main_action current_action = BPSTAT_WHAT_KEEP_CHECKING; struct bpstat_what retval; @@ -3470,6 +3475,9 @@ bpstat_what (bpstat bs) 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: @@ -3603,6 +3611,7 @@ print_one_breakpoint_location (struct breakpoint *b, {bp_longjmp_master, "longjmp master"}, {bp_catchpoint, "catchpoint"}, {bp_tracepoint, "tracepoint"}, + {bp_jit_event, "jit events"}, }; static char bpenables[] = "nynny"; @@ -3731,6 +3740,7 @@ print_one_breakpoint_location (struct breakpoint *b, case bp_overlay_event: case bp_longjmp_master: case bp_tracepoint: + case bp_jit_event: if (opts.addressprint) { annotate_field (4); @@ -4375,6 +4385,7 @@ allocate_bp_location (struct breakpoint *bpt) 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; @@ -4657,6 +4668,17 @@ struct lang_and_radix 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) @@ -5338,6 +5360,7 @@ mention (struct breakpoint *b) case bp_shlib_event: case bp_thread_event: case bp_overlay_event: + case bp_jit_event: case bp_longjmp_master: break; } @@ -7654,6 +7677,7 @@ delete_command (char *arg, int from_tty) { 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 @@ -7673,6 +7697,7 @@ delete_command (char *arg, int from_tty) 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) @@ -7999,6 +8024,7 @@ breakpoint_re_set_one (void *bint) case bp_step_resume: case bp_longjmp: case bp_longjmp_resume: + case bp_jit_event: break; } @@ -8027,6 +8053,8 @@ breakpoint_re_set (void) 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"); diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h index d93c6b6..70b1398 100644 --- a/gdb/breakpoint.h +++ b/gdb/breakpoint.h @@ -120,6 +120,9 @@ enum bptype bp_catchpoint, bp_tracepoint, + + /* Event for JIT compiled code generation or deletion. */ + bp_jit_event, }; /* States of enablement of breakpoint. */ @@ -554,6 +557,9 @@ enum bpstat_what_main_action 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 }; @@ -865,6 +871,9 @@ extern void mark_breakpoints_out (void); 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); diff --git a/gdb/doc/ChangeLog b/gdb/doc/ChangeLog index 2801a5c..3373216 100644 --- a/gdb/doc/ChangeLog +++ b/gdb/doc/ChangeLog @@ -1,3 +1,7 @@ +2009-08-20 Reid Kleckner <reid@kleckner.net> + + * gdb.texinfo: Add chapter on JIT interface. + 2009-08-07 Nick Roberts <nickrob@snap.net.nz> * gdb.texinfo (Server Prefix): Explain that server prefix suppresses diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 4016acc..e5fe6ac 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -159,6 +159,7 @@ software in general. We will miss him. * 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} @@ -25921,6 +25922,136 @@ source which is being displayed. @var{addr} is in the form @samp{0x} 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} diff --git a/gdb/infrun.c b/gdb/infrun.c index e3eddce..892e0d4 100644 --- a/gdb/infrun.c +++ b/gdb/infrun.c @@ -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 @@ follow_exec (ptid_t pid, char *execd_pathname) 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...) */ @@ -3540,6 +3543,22 @@ infrun: BPSTAT_WHAT_SET_LONGJMP_RESUME (!gdbarch_get_longjmp_target)\n"); } 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. */ diff --git a/gdb/jit.c b/gdb/jit.c new file mode 100644 index 0000000..0c50060 --- /dev/null +++ b/gdb/jit.c @@ -0,0 +1,438 @@ +/* Handle JIT code generation in the inferior for GDB, the GNU Debugger. + + Copyright (C) 2009 + Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "defs.h" + +#include "jit.h" +#include "breakpoint.h" +#include "gdbcore.h" +#include "observer.h" +#include "objfiles.h" +#include "symfile.h" +#include "symtab.h" +#include "target.h" +#include "gdb_stat.h" + +static const struct objfile_data *jit_objfile_data; + +static const char *const jit_break_name = "__jit_debug_register_code"; + +static const char *const jit_descriptor_name = "__jit_debug_descriptor"; + +/* This is the address of the JIT descriptor in the inferior. */ + +static CORE_ADDR jit_descriptor_addr = 0; + +/* This is a boolean indicating whether we're currently registering code. This + is used to avoid re-entering the registration code. We want to check for + new JITed every time a new object file is loaded, but we want to avoid + checking for new code while we're registering object files for JITed code. + Therefore, we flip this variable to 1 before registering new object files, + and set it to 0 before returning. */ + +static int registering_code = 0; + +/* Helper cleanup function to clear an integer flag like the one above. */ + +static void +clear_int (void *int_addr) +{ + *((int *) int_addr) = 0; +} + +struct target_buffer +{ + CORE_ADDR base; + size_t size; +}; + +/* Openning the file is a no-op. */ + +static void * +mem_bfd_iovec_open (struct bfd *abfd, void *open_closure) +{ + return open_closure; +} + +/* Closing the file is just freeing the base/size pair on our side. */ + +static int +mem_bfd_iovec_close (struct bfd *abfd, void *stream) +{ + xfree (stream); + return 1; +} + +/* For reading the file, we just need to pass through to target_read_memory and + fix up the arguments and return values. */ + +static file_ptr +mem_bfd_iovec_pread (struct bfd *abfd, void *stream, void *buf, + file_ptr nbytes, file_ptr offset) +{ + int err; + struct target_buffer *buffer = (struct target_buffer *) stream; + + /* If this read will read all of the file, limit it to just the rest. */ + if (offset + nbytes > buffer->size) + nbytes = buffer->size - offset; + + /* If there are no more bytes left, we've reached EOF. */ + if (nbytes == 0) + return 0; + + err = target_read_memory (buffer->base + offset, (gdb_byte *) buf, nbytes); + if (err) + return -1; + + return nbytes; +} + +/* For statting the file, we only support the st_size attribute. */ + +static int +mem_bfd_iovec_stat (struct bfd *abfd, void *stream, struct stat *sb) +{ + struct target_buffer *buffer = (struct target_buffer*) stream; + + sb->st_size = buffer->size; + return 0; +} + +/* Open a BFD from the target's memory. */ + +static struct bfd * +bfd_open_from_target_memory (CORE_ADDR addr, size_t size, char *target) +{ + const char *filename = xstrdup ("<in-memory>"); + struct target_buffer *buffer = xmalloc (sizeof (struct target_buffer)); + + buffer->base = addr; + buffer->size = size; + return bfd_openr_iovec (filename, target, + mem_bfd_iovec_open, + buffer, + mem_bfd_iovec_pread, + mem_bfd_iovec_close, + mem_bfd_iovec_stat); +} + +/* Helper function for reading the global JIT descriptor from remote memory. */ + +static void +jit_read_descriptor (struct jit_descriptor *descriptor) +{ + int err; + struct type *ptr_type; + int ptr_size; + int desc_size; + gdb_byte *desc_buf; + enum bfd_endian byte_order = gdbarch_byte_order (target_gdbarch); + + /* Figure out how big the descriptor is on the remote and how to read it. */ + ptr_type = builtin_type (target_gdbarch)->builtin_data_ptr; + ptr_size = TYPE_LENGTH (ptr_type); + desc_size = 8 + 2 * ptr_size; /* Two 32-bit ints and two pointers. */ + desc_buf = alloca (desc_size); + + /* Read the descriptor. */ + err = target_read_memory (jit_descriptor_addr, desc_buf, desc_size); + if (err) + error (_("Unable to read JIT descriptor from remote memory!")); + + /* Fix the endianness to match the host. */ + descriptor->version = extract_unsigned_integer (&desc_buf[0], 4, byte_order); + descriptor->action_flag = + extract_unsigned_integer (&desc_buf[4], 4, byte_order); + descriptor->relevant_entry = extract_typed_address (&desc_buf[8], ptr_type); + descriptor->first_entry = + extract_typed_address (&desc_buf[8 + ptr_size], ptr_type); +} + +/* Helper function for reading a JITed code entry from remote memory. */ + +static void +jit_read_code_entry (CORE_ADDR code_addr, struct jit_code_entry *code_entry) +{ + int err; + struct type *ptr_type; + int ptr_size; + int entry_size; + gdb_byte *entry_buf; + enum bfd_endian byte_order = gdbarch_byte_order (target_gdbarch); + + /* Figure out how big the entry is on the remote and how to read it. */ + ptr_type = builtin_type (target_gdbarch)->builtin_data_ptr; + ptr_size = TYPE_LENGTH (ptr_type); + entry_size = 3 * ptr_size + 8; /* Three pointers and one 64-bit int. */ + entry_buf = alloca (entry_size); + + /* Read the entry. */ + err = target_read_memory (code_addr, entry_buf, entry_size); + if (err) + error (_("Unable to read JIT code entry from remote memory!")); + + /* Fix the endianness to match the host. */ + ptr_type = builtin_type (target_gdbarch)->builtin_data_ptr; + code_entry->next_entry = extract_typed_address (&entry_buf[0], ptr_type); + code_entry->prev_entry = + extract_typed_address (&entry_buf[ptr_size], ptr_type); + code_entry->symfile_addr = + extract_typed_address (&entry_buf[2 * ptr_size], ptr_type); + code_entry->symfile_size = + extract_unsigned_integer (&entry_buf[3 * ptr_size], 8, byte_order); +} + +/* This function registers code associated with a JIT code entry. It uses the + pointer and size pair in the entry to read the symbol file from the remote + and then calls symbol_file_add_from_local_memory to add it as though it were + a symbol file added by the user. */ + +static void +jit_register_code (CORE_ADDR entry_addr, struct jit_code_entry *code_entry) +{ + bfd *nbfd; + struct section_addr_info *sai; + struct bfd_section *sec; + struct objfile *objfile; + struct cleanup *old_cleanups, *my_cleanups; + int i; + const struct bfd_arch_info *b; + CORE_ADDR *entry_addr_ptr; + + nbfd = bfd_open_from_target_memory (code_entry->symfile_addr, + code_entry->symfile_size, gnutarget); + old_cleanups = make_cleanup_bfd_close (nbfd); + + /* Check the format. NOTE: This initializes important data that GDB uses! + We would segfault later without this line. */ + if (!bfd_check_format (nbfd, bfd_object)) + { + printf_unfiltered (_("\ +JITed symbol file is not an object file, ignoring it.\n")); + do_cleanups (old_cleanups); + return; + } + + /* Check bfd arch. */ + b = gdbarch_bfd_arch_info (target_gdbarch); + if (b->compatible (b, bfd_get_arch_info (nbfd)) != b) + warning (_("JITed object file architecture %s is not compatible " + "with target architecture %s."), bfd_get_arch_info + (nbfd)->printable_name, b->printable_name); + + /* Read the section address information out of the symbol file. Since the + file is generated by the JIT at runtime, it should all of the absolute + addresses that we care about. */ + sai = alloc_section_addr_info (bfd_count_sections (nbfd)); + make_cleanup_free_section_addr_info (sai); + i = 0; + for (sec = nbfd->sections; sec != NULL; sec = sec->next) + if ((bfd_get_section_flags (nbfd, sec) & (SEC_ALLOC|SEC_LOAD)) != 0) + { + /* We assume that these virtual addresses are absolute, and do not + treat them as offsets. */ + sai->other[i].addr = bfd_get_section_vma (nbfd, sec); + sai->other[i].name = (char *) bfd_get_section_name (nbfd, sec); + sai->other[i].sectindex = sec->index; + ++i; + } + + /* Raise this flag while we register code so we won't trigger any + re-registration. */ + registering_code = 1; + my_cleanups = make_cleanup (clear_int, ®istering_code); + + /* This call takes ownership of sai. */ + objfile = symbol_file_add_from_bfd (nbfd, 0, sai, OBJF_SHARED); + + /* Clear the registering_code flag. */ + do_cleanups (my_cleanups); + + /* Remember a mapping from entry_addr to objfile. */ + entry_addr_ptr = xmalloc (sizeof (CORE_ADDR)); + *entry_addr_ptr = entry_addr; + set_objfile_data (objfile, jit_objfile_data, entry_addr_ptr); + + discard_cleanups (old_cleanups); +} + +/* This function unregisters JITed code and frees the corresponding objfile. */ + +static void +jit_unregister_code (struct objfile *objfile) +{ + free_objfile (objfile); +} + +/* Look up the objfile with this code entry address. */ + +static struct objfile * +jit_find_objf_with_entry_addr (CORE_ADDR entry_addr) +{ + struct objfile *objf; + CORE_ADDR *objf_entry_addr; + + ALL_OBJFILES (objf) + { + objf_entry_addr = (CORE_ADDR *) objfile_data (objf, jit_objfile_data); + if (objf_entry_addr != NULL && *objf_entry_addr == entry_addr) + return objf; + } + return NULL; +} + +void +jit_inferior_created_hook (void) +{ + struct minimal_symbol *reg_symbol; + struct minimal_symbol *desc_symbol; + CORE_ADDR reg_addr; + struct jit_descriptor descriptor; + struct jit_code_entry cur_entry; + CORE_ADDR cur_entry_addr; + struct cleanup *old_cleanups; + + /* When we register code, GDB resets its breakpoints in case symbols have + changed. That in turn calls this handler, which makes us look for new + code again. To avoid being re-entered, we check this flag. */ + if (registering_code) + return; + + /* Lookup the registration symbol. If it is missing, then we assume we are + not attached to a JIT. */ + reg_symbol = lookup_minimal_symbol (jit_break_name, NULL, NULL); + if (reg_symbol == NULL) + return; + reg_addr = SYMBOL_VALUE_ADDRESS (reg_symbol); + if (reg_addr == 0) + return; + + /* Lookup the descriptor symbol and cache the addr. If it is missing, we + assume we are not attached to a JIT and return early. */ + desc_symbol = lookup_minimal_symbol (jit_descriptor_name, NULL, NULL); + if (desc_symbol == NULL) + return; + jit_descriptor_addr = SYMBOL_VALUE_ADDRESS (desc_symbol); + if (jit_descriptor_addr == 0) + return; + + /* Read the descriptor so we can check the version number and load any already + JITed functions. */ + jit_read_descriptor (&descriptor); + + /* Check that the version number agrees with that we support. */ + if (descriptor.version != 1) + error (_("Unsupported JIT protocol version in descriptor!")); + + /* Put a breakpoint in the registration symbol. */ + create_jit_event_breakpoint (target_gdbarch, reg_addr); + + /* If we've attached to a running program, we need to check the descriptor to + register any functions that were already generated. */ + for (cur_entry_addr = descriptor.first_entry; + cur_entry_addr != 0; + cur_entry_addr = cur_entry.next_entry) + { + jit_read_code_entry (cur_entry_addr, &cur_entry); + + /* This hook may be called many times during setup, so make sure we don't + add the same symbol file twice. */ + if (jit_find_objf_with_entry_addr (cur_entry_addr) != NULL) + continue; + + jit_register_code (cur_entry_addr, &cur_entry); + } +} + +/* Wrapper to match the observer function pointer prototype. */ + +static void +jit_inferior_created_hook1 (struct target_ops *objfile, int from_tty) +{ + jit_inferior_created_hook (); +} + +/* This function cleans up any code entries left over when the inferior exits. + We get left over code when the inferior exits without unregistering its code, + for example when it crashes. */ + +static void +jit_inferior_exit_hook (int pid) +{ + struct objfile *objf; + struct objfile *temp; + + /* We need to reset the descriptor addr so that next time we load up the + inferior we look for it again. */ + jit_descriptor_addr = 0; + + ALL_OBJFILES_SAFE (objf, temp) + if (objfile_data (objf, jit_objfile_data) != NULL) + jit_unregister_code (objf); +} + +void +jit_event_handler (void) +{ + struct jit_descriptor descriptor; + struct jit_code_entry code_entry; + CORE_ADDR entry_addr; + struct objfile *objf; + + /* Read the descriptor from remote memory. */ + jit_read_descriptor (&descriptor); + entry_addr = descriptor.relevant_entry; + + /* Do the corresponding action. */ + switch (descriptor.action_flag) + { + case JIT_NOACTION: + break; + case JIT_REGISTER: + jit_read_code_entry (entry_addr, &code_entry); + jit_register_code (entry_addr, &code_entry); + break; + case JIT_UNREGISTER: + objf = jit_find_objf_with_entry_addr (entry_addr); + if (objf == NULL) + printf_unfiltered ("Unable to find JITed code entry at address: %p\n", + (void *) entry_addr); + else + jit_unregister_code (objf); + + break; + default: + error (_("Unknown action_flag value in JIT descriptor!")); + break; + } +} + +/* Provide a prototype to silence -Wmissing-prototypes. */ + +extern void _initialize_jit (void); + +void +_initialize_jit (void) +{ + observer_attach_inferior_created (jit_inferior_created_hook1); + observer_attach_inferior_exit (jit_inferior_exit_hook); + jit_objfile_data = register_objfile_data (); +} diff --git a/gdb/jit.h b/gdb/jit.h new file mode 100644 index 0000000..6473d25 --- /dev/null +++ b/gdb/jit.h @@ -0,0 +1,77 @@ +/* JIT declarations for GDB, the GNU Debugger. + + Copyright (C) 2009 + Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef JIT_H +#define JIT_H + +/* When the JIT breakpoint fires, the inferior wants us to take one of these + actions. These values are used by the inferior, so the values of these enums + cannot be changed. */ + +typedef enum +{ + JIT_NOACTION = 0, + JIT_REGISTER, + JIT_UNREGISTER +} jit_actions_t; + +/* This struct describes a single symbol file in a linked list of symbol files + describing generated code. As the inferior generates code, it adds these + entries to the list, and when we attach to the inferior, we read them all. + For the first element prev_entry should be NULL, and for the last element + next_entry should be NULL. */ + +struct jit_code_entry +{ + CORE_ADDR next_entry; + CORE_ADDR prev_entry; + CORE_ADDR symfile_addr; + uint64_t symfile_size; +}; + +/* This is the global descriptor that the inferior uses to communicate + information to the debugger. To alert the debugger to take an action, the + inferior sets the action_flag to the appropriate enum value, updates + relevant_entry to point to the relevant code entry, and calls the function at + the well-known symbol with our breakpoint. We then read this descriptor from + another global well-known symbol. */ + +struct jit_descriptor +{ + uint32_t version; + /* This should be jit_actions_t, but we want to be specific about the + bit-width. */ + uint32_t action_flag; + CORE_ADDR relevant_entry; + CORE_ADDR first_entry; +}; + +/* Looks for the descriptor and registration symbols and breakpoints the + registration function. If it finds both, it registers all the already JITed + code. If it has already found the symbols, then it doesn't try again. */ + +extern void jit_inferior_created_hook (void); + +/* This function is called by handle_inferior_event when it decides that the JIT + event breakpoint has fired. */ + +extern void jit_event_handler (void); + +#endif /* JIT_H */ |