aboutsummaryrefslogtreecommitdiff
path: root/gdb/jit.c
diff options
context:
space:
mode:
authorDoug Evans <dje@google.com>2009-08-20 18:02:48 +0000
committerDoug Evans <dje@google.com>2009-08-20 18:02:48 +0000
commit4efc6507960ac76505ebb1be9886f207ceb46c3a (patch)
treeaf68d7cb89746890d09bd44080fe6816ca12bb1c /gdb/jit.c
parentc469dcaa811e4c7d781f6ba9e9dd25fab234604f (diff)
downloadgdb-4efc6507960ac76505ebb1be9886f207ceb46c3a.zip
gdb-4efc6507960ac76505ebb1be9886f207ceb46c3a.tar.gz
gdb-4efc6507960ac76505ebb1be9886f207ceb46c3a.tar.bz2
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. * doc/gdb.texinfo: Add chapter on JIT interface.
Diffstat (limited to 'gdb/jit.c')
-rw-r--r--gdb/jit.c438
1 files changed, 438 insertions, 0 deletions
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, &registering_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 ();
+}