/* Handle JIT code generation in the inferior for GDB, the GNU Debugger.
- Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+ Copyright (C) 2009-2013 Free Software Foundation, Inc.
This file is part of GDB.
#include "defs.h"
#include "jit.h"
+#include "jit-reader.h"
+#include "block.h"
#include "breakpoint.h"
+#include "command.h"
+#include "dictionary.h"
+#include "filenames.h"
+#include "frame-unwind.h"
+#include "gdbcmd.h"
#include "gdbcore.h"
+#include "inferior.h"
#include "observer.h"
#include "objfiles.h"
+#include "regcache.h"
#include "symfile.h"
#include "symtab.h"
#include "target.h"
+#include "gdb-dlfcn.h"
#include "gdb_stat.h"
+#include "exceptions.h"
+#include "gdb_bfd.h"
+
+static const char *jit_reader_dir = NULL;
static const struct objfile_data *jit_objfile_data;
static const char *const jit_descriptor_name = "__jit_debug_descriptor";
-/* This is the address of the JIT descriptor in the inferior. */
+static const struct program_space_data *jit_program_space_data = NULL;
+
+static void jit_inferior_init (struct gdbarch *gdbarch);
-static CORE_ADDR jit_descriptor_addr = 0;
+/* An unwinder is registered for every gdbarch. This key is used to
+ remember if the unwinder has been registered for a particular
+ gdbarch. */
-/* 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 struct gdbarch_data *jit_gdbarch_data;
-static int registering_code = 0;
+/* Non-zero if we want to see trace of jit level stuff. */
-/* Helper cleanup function to clear an integer flag like the one above. */
+static unsigned int jit_debug = 0;
static void
-clear_int (void *int_addr)
+show_jit_debug (struct ui_file *file, int from_tty,
+ struct cmd_list_element *c, const char *value)
{
- *((int *) int_addr) = 0;
+ fprintf_filtered (file, _("JIT debugging is %s.\n"), value);
}
struct target_buffer
{
CORE_ADDR base;
- size_t size;
+ ULONGEST size;
};
/* Openning the file is a no-op. */
mem_bfd_iovec_close (struct bfd *abfd, void *stream)
{
xfree (stream);
- return 1;
+
+ /* Zero means success. */
+ return 0;
}
/* For reading the file, we just need to pass through to target_read_memory and
/* Open a BFD from the target's memory. */
static struct bfd *
-bfd_open_from_target_memory (CORE_ADDR addr, size_t size, char *target)
+bfd_open_from_target_memory (CORE_ADDR addr, ULONGEST 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);
+ return gdb_bfd_openr_iovec ("<in-memory>", 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. */
+/* One reader that has been loaded successfully, and can potentially be used to
+ parse debug info. */
+
+static struct jit_reader
+{
+ struct gdb_reader_funcs *functions;
+ void *handle;
+} *loaded_jit_reader = NULL;
+
+typedef struct gdb_reader_funcs * (reader_init_fn_type) (void);
+static const char *reader_init_fn_sym = "gdb_init_reader";
+
+/* Try to load FILE_NAME as a JIT debug info reader. */
+
+static struct jit_reader *
+jit_reader_load (const char *file_name)
+{
+ void *so;
+ reader_init_fn_type *init_fn;
+ struct jit_reader *new_reader = NULL;
+ struct gdb_reader_funcs *funcs = NULL;
+ struct cleanup *old_cleanups;
+
+ if (jit_debug)
+ fprintf_unfiltered (gdb_stdlog, _("Opening shared object %s.\n"),
+ file_name);
+ so = gdb_dlopen (file_name);
+ old_cleanups = make_cleanup_dlclose (so);
+
+ init_fn = gdb_dlsym (so, reader_init_fn_sym);
+ if (!init_fn)
+ error (_("Could not locate initialization function: %s."),
+ reader_init_fn_sym);
+
+ if (gdb_dlsym (so, "plugin_is_GPL_compatible") == NULL)
+ error (_("Reader not GPL compatible."));
+
+ funcs = init_fn ();
+ if (funcs->reader_version != GDB_READER_INTERFACE_VERSION)
+ error (_("Reader version does not match GDB version."));
+
+ new_reader = XZALLOC (struct jit_reader);
+ new_reader->functions = funcs;
+ new_reader->handle = so;
+
+ discard_cleanups (old_cleanups);
+ return new_reader;
+}
+
+/* Provides the jit-reader-load command. */
static void
+jit_reader_load_command (char *args, int from_tty)
+{
+ char *so_name;
+ struct cleanup *prev_cleanup;
+
+ if (args == NULL)
+ error (_("No reader name provided."));
+
+ if (loaded_jit_reader != NULL)
+ error (_("JIT reader already loaded. Run jit-reader-unload first."));
+
+ if (IS_ABSOLUTE_PATH (args))
+ so_name = xstrdup (args);
+ else
+ so_name = xstrprintf ("%s%s%s", SLASH_STRING, jit_reader_dir, args);
+ prev_cleanup = make_cleanup (xfree, so_name);
+
+ loaded_jit_reader = jit_reader_load (so_name);
+ do_cleanups (prev_cleanup);
+}
+
+/* Provides the jit-reader-unload command. */
+
+static void
+jit_reader_unload_command (char *args, int from_tty)
+{
+ if (!loaded_jit_reader)
+ error (_("No JIT reader loaded."));
+
+ loaded_jit_reader->functions->destroy (loaded_jit_reader->functions);
+
+ gdb_dlclose (loaded_jit_reader->handle);
+ xfree (loaded_jit_reader);
+ loaded_jit_reader = NULL;
+}
+
+/* Per-program space structure recording which objfile has the JIT
+ symbols. */
+
+struct jit_program_space_data
+{
+ /* The objfile. This is NULL if no objfile holds the JIT
+ symbols. */
+
+ struct objfile *objfile;
+
+ /* If this program space has __jit_debug_register_code, this is the
+ cached address from the minimal symbol. This is used to detect
+ relocations requiring the breakpoint to be re-created. */
+
+ CORE_ADDR cached_code_address;
+
+ /* This is the JIT event breakpoint, or NULL if it has not been
+ set. */
+
+ struct breakpoint *jit_breakpoint;
+};
+
+/* Per-objfile structure recording the addresses in the program space.
+ This object serves two purposes: for ordinary objfiles, it may
+ cache some symbols related to the JIT interface; and for
+ JIT-created objfiles, it holds some information about the
+ jit_code_entry. */
+
+struct jit_objfile_data
+{
+ /* Symbol for __jit_debug_register_code. */
+ struct minimal_symbol *register_code;
+
+ /* Symbol for __jit_debug_descriptor. */
+ struct minimal_symbol *descriptor;
+
+ /* Address of struct jit_code_entry in this objfile. This is only
+ non-zero for objfiles that represent code created by the JIT. */
+ CORE_ADDR addr;
+};
+
+/* Fetch the jit_objfile_data associated with OBJF. If no data exists
+ yet, make a new structure and attach it. */
+
+static struct jit_objfile_data *
+get_jit_objfile_data (struct objfile *objf)
+{
+ struct jit_objfile_data *objf_data;
+
+ objf_data = objfile_data (objf, jit_objfile_data);
+ if (objf_data == NULL)
+ {
+ objf_data = XZALLOC (struct jit_objfile_data);
+ set_objfile_data (objf, jit_objfile_data, objf_data);
+ }
+
+ return objf_data;
+}
+
+/* Remember OBJFILE has been created for struct jit_code_entry located
+ at inferior address ENTRY. */
+
+static void
+add_objfile_entry (struct objfile *objfile, CORE_ADDR entry)
+{
+ struct jit_objfile_data *objf_data;
+
+ objf_data = get_jit_objfile_data (objfile);
+ objf_data->addr = entry;
+}
+
+/* Return jit_program_space_data for current program space. Allocate
+ if not already present. */
+
+static struct jit_program_space_data *
+get_jit_program_space_data (void)
+{
+ struct jit_program_space_data *ps_data;
+
+ ps_data = program_space_data (current_program_space, jit_program_space_data);
+ if (ps_data == NULL)
+ {
+ ps_data = XZALLOC (struct jit_program_space_data);
+ set_program_space_data (current_program_space, jit_program_space_data,
+ ps_data);
+ }
+
+ return ps_data;
+}
+
+static void
+jit_program_space_data_cleanup (struct program_space *ps, void *arg)
+{
+ xfree (arg);
+}
+
+/* Helper function for reading the global JIT descriptor from remote
+ memory. Returns 1 if all went well, 0 otherwise. */
+
+static int
jit_read_descriptor (struct gdbarch *gdbarch,
- struct jit_descriptor *descriptor)
+ struct jit_descriptor *descriptor,
+ struct jit_program_space_data *ps_data)
{
int err;
struct type *ptr_type;
int desc_size;
gdb_byte *desc_buf;
enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+ struct jit_objfile_data *objf_data;
+
+ if (ps_data->objfile == NULL)
+ return 0;
+ objf_data = get_jit_objfile_data (ps_data->objfile);
+ if (objf_data->descriptor == NULL)
+ return 0;
+
+ if (jit_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "jit_read_descriptor, descriptor_addr = %s\n",
+ paddress (gdbarch, SYMBOL_VALUE_ADDRESS (objf_data->descriptor)));
/* Figure out how big the descriptor is on the remote and how to read it. */
ptr_type = builtin_type (gdbarch)->builtin_data_ptr;
desc_buf = alloca (desc_size);
/* Read the descriptor. */
- err = target_read_memory (jit_descriptor_addr, desc_buf, desc_size);
+ err = target_read_memory (SYMBOL_VALUE_ADDRESS (objf_data->descriptor),
+ desc_buf, desc_size);
if (err)
- error (_("Unable to read JIT descriptor from remote memory!"));
+ {
+ printf_unfiltered (_("Unable to read JIT descriptor from "
+ "remote memory\n"));
+ return 0;
+ }
/* Fix the endianness to match the host. */
descriptor->version = extract_unsigned_integer (&desc_buf[0], 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);
+
+ return 1;
}
/* Helper function for reading a JITed code entry from remote memory. */
jit_read_code_entry (struct gdbarch *gdbarch,
CORE_ADDR code_addr, struct jit_code_entry *code_entry)
{
- int err;
+ int err, off;
struct type *ptr_type;
int ptr_size;
int entry_size;
+ int align_bytes;
gdb_byte *entry_buf;
enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
/* Figure out how big the entry is on the remote and how to read it. */
ptr_type = builtin_type (gdbarch)->builtin_data_ptr;
ptr_size = TYPE_LENGTH (ptr_type);
- entry_size = 3 * ptr_size + 8; /* Three pointers and one 64-bit int. */
+
+ /* Figure out where the longlong value will be. */
+ align_bytes = gdbarch_long_long_align_bit (gdbarch) / 8;
+ off = 3 * ptr_size;
+ off = (off + (align_bytes - 1)) & ~(align_bytes - 1);
+
+ entry_size = off + 8; /* Three pointers and one 64-bit int. */
entry_buf = alloca (entry_size);
/* Read the entry. */
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);
+ extract_unsigned_integer (&entry_buf[off], 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. */
+/* Proxy object for building a block. */
+
+struct gdb_block
+{
+ /* gdb_blocks are linked into a tree structure. Next points to the
+ next node at the same depth as this block and parent to the
+ parent gdb_block. */
+ struct gdb_block *next, *parent;
+
+ /* Points to the "real" block that is being built out of this
+ instance. This block will be added to a blockvector, which will
+ then be added to a symtab. */
+ struct block *real_block;
+
+ /* The first and last code address corresponding to this block. */
+ CORE_ADDR begin, end;
+
+ /* The name of this block (if any). If this is non-NULL, the
+ FUNCTION symbol symbol is set to this value. */
+ const char *name;
+};
+
+/* Proxy object for building a symtab. */
+
+struct gdb_symtab
+{
+ /* The list of blocks in this symtab. These will eventually be
+ converted to real blocks. */
+ struct gdb_block *blocks;
+
+ /* The number of blocks inserted. */
+ int nblocks;
+
+ /* A mapping between line numbers to PC. */
+ struct linetable *linetable;
+
+ /* The source file for this symtab. */
+ const char *file_name;
+ struct gdb_symtab *next;
+};
+
+/* Proxy object for building an object. */
+
+struct gdb_object
+{
+ struct gdb_symtab *symtabs;
+};
+
+/* The type of the `private' data passed around by the callback
+ functions. */
+
+typedef CORE_ADDR jit_dbg_reader_data;
+
+/* The reader calls into this function to read data off the targets
+ address space. */
+
+static enum gdb_status
+jit_target_read_impl (GDB_CORE_ADDR target_mem, void *gdb_buf, int len)
+{
+ int result = target_read_memory ((CORE_ADDR) target_mem, gdb_buf, len);
+ if (result == 0)
+ return GDB_SUCCESS;
+ else
+ return GDB_FAIL;
+}
+
+/* The reader calls into this function to create a new gdb_object
+ which it can then pass around to the other callbacks. Right now,
+ all that is required is allocating the memory. */
+
+static struct gdb_object *
+jit_object_open_impl (struct gdb_symbol_callbacks *cb)
+{
+ /* CB is not required right now, but sometime in the future we might
+ need a handle to it, and we'd like to do that without breaking
+ the ABI. */
+ return XZALLOC (struct gdb_object);
+}
+
+/* Readers call into this function to open a new gdb_symtab, which,
+ again, is passed around to other callbacks. */
+
+static struct gdb_symtab *
+jit_symtab_open_impl (struct gdb_symbol_callbacks *cb,
+ struct gdb_object *object,
+ const char *file_name)
+{
+ struct gdb_symtab *ret;
+
+ /* CB stays unused. See comment in jit_object_open_impl. */
+
+ ret = XZALLOC (struct gdb_symtab);
+ ret->file_name = file_name ? xstrdup (file_name) : xstrdup ("");
+ ret->next = object->symtabs;
+ object->symtabs = ret;
+ return ret;
+}
+
+/* Returns true if the block corresponding to old should be placed
+ before the block corresponding to new in the final blockvector. */
+
+static int
+compare_block (const struct gdb_block *const old,
+ const struct gdb_block *const new)
+{
+ if (old == NULL)
+ return 1;
+ if (old->begin < new->begin)
+ return 1;
+ else if (old->begin == new->begin)
+ {
+ if (old->end > new->end)
+ return 1;
+ else
+ return 0;
+ }
+ else
+ return 0;
+}
+
+/* Called by readers to open a new gdb_block. This function also
+ inserts the new gdb_block in the correct place in the corresponding
+ gdb_symtab. */
+
+static struct gdb_block *
+jit_block_open_impl (struct gdb_symbol_callbacks *cb,
+ struct gdb_symtab *symtab, struct gdb_block *parent,
+ GDB_CORE_ADDR begin, GDB_CORE_ADDR end, const char *name)
+{
+ struct gdb_block *block = XZALLOC (struct gdb_block);
+
+ block->next = symtab->blocks;
+ block->begin = (CORE_ADDR) begin;
+ block->end = (CORE_ADDR) end;
+ block->name = name ? xstrdup (name) : NULL;
+ block->parent = parent;
+
+ /* Ensure that the blocks are inserted in the correct (reverse of
+ the order expected by blockvector). */
+ if (compare_block (symtab->blocks, block))
+ {
+ symtab->blocks = block;
+ }
+ else
+ {
+ struct gdb_block *i = symtab->blocks;
+
+ for (;; i = i->next)
+ {
+ /* Guaranteed to terminate, since compare_block (NULL, _)
+ returns 1. */
+ if (compare_block (i->next, block))
+ {
+ block->next = i->next;
+ i->next = block;
+ break;
+ }
+ }
+ }
+ symtab->nblocks++;
+
+ return block;
+}
+
+/* Readers call this to add a line mapping (from PC to line number) to
+ a gdb_symtab. */
static void
-jit_register_code (struct gdbarch *gdbarch,
- CORE_ADDR entry_addr, struct jit_code_entry *code_entry)
+jit_symtab_line_mapping_add_impl (struct gdb_symbol_callbacks *cb,
+ struct gdb_symtab *stab, int nlines,
+ struct gdb_line_mapping *map)
+{
+ int i;
+
+ if (nlines < 1)
+ return;
+
+ stab->linetable = xmalloc (sizeof (struct linetable)
+ + (nlines - 1) * sizeof (struct linetable_entry));
+ stab->linetable->nitems = nlines;
+ for (i = 0; i < nlines; i++)
+ {
+ stab->linetable->item[i].pc = (CORE_ADDR) map[i].pc;
+ stab->linetable->item[i].line = map[i].line;
+ }
+}
+
+/* Called by readers to close a gdb_symtab. Does not need to do
+ anything as of now. */
+
+static void
+jit_symtab_close_impl (struct gdb_symbol_callbacks *cb,
+ struct gdb_symtab *stab)
+{
+ /* Right now nothing needs to be done here. We may need to do some
+ cleanup here in the future (again, without breaking the plugin
+ ABI). */
+}
+
+/* Transform STAB to a proper symtab, and add it it OBJFILE. */
+
+static void
+finalize_symtab (struct gdb_symtab *stab, struct objfile *objfile)
+{
+ struct symtab *symtab;
+ struct gdb_block *gdb_block_iter, *gdb_block_iter_tmp;
+ struct block *block_iter;
+ int actual_nblocks, i, blockvector_size;
+ CORE_ADDR begin, end;
+
+ actual_nblocks = FIRST_LOCAL_BLOCK + stab->nblocks;
+
+ symtab = allocate_symtab (stab->file_name, objfile);
+ /* JIT compilers compile in memory. */
+ symtab->dirname = NULL;
+
+ /* Copy over the linetable entry if one was provided. */
+ if (stab->linetable)
+ {
+ int size = ((stab->linetable->nitems - 1)
+ * sizeof (struct linetable_entry)
+ + sizeof (struct linetable));
+ LINETABLE (symtab) = obstack_alloc (&objfile->objfile_obstack, size);
+ memcpy (LINETABLE (symtab), stab->linetable, size);
+ }
+ else
+ {
+ LINETABLE (symtab) = NULL;
+ }
+
+ blockvector_size = (sizeof (struct blockvector)
+ + (actual_nblocks - 1) * sizeof (struct block *));
+ symtab->blockvector = obstack_alloc (&objfile->objfile_obstack,
+ blockvector_size);
+
+ /* (begin, end) will contain the PC range this entire blockvector
+ spans. */
+ symtab->primary = 1;
+ BLOCKVECTOR_MAP (symtab->blockvector) = NULL;
+ begin = stab->blocks->begin;
+ end = stab->blocks->end;
+ BLOCKVECTOR_NBLOCKS (symtab->blockvector) = actual_nblocks;
+
+ /* First run over all the gdb_block objects, creating a real block
+ object for each. Simultaneously, keep setting the real_block
+ fields. */
+ for (i = (actual_nblocks - 1), gdb_block_iter = stab->blocks;
+ i >= FIRST_LOCAL_BLOCK;
+ i--, gdb_block_iter = gdb_block_iter->next)
+ {
+ struct block *new_block = allocate_block (&objfile->objfile_obstack);
+ struct symbol *block_name = allocate_symbol (objfile);
+ struct type *block_type = arch_type (get_objfile_arch (objfile),
+ TYPE_CODE_VOID,
+ 1,
+ "void");
+
+ BLOCK_DICT (new_block) = dict_create_linear (&objfile->objfile_obstack,
+ NULL);
+ /* The address range. */
+ BLOCK_START (new_block) = (CORE_ADDR) gdb_block_iter->begin;
+ BLOCK_END (new_block) = (CORE_ADDR) gdb_block_iter->end;
+
+ /* The name. */
+ SYMBOL_DOMAIN (block_name) = VAR_DOMAIN;
+ SYMBOL_ACLASS_INDEX (block_name) = LOC_BLOCK;
+ SYMBOL_SYMTAB (block_name) = symtab;
+ SYMBOL_TYPE (block_name) = lookup_function_type (block_type);
+ SYMBOL_BLOCK_VALUE (block_name) = new_block;
+
+ block_name->ginfo.name = obstack_copy0 (&objfile->objfile_obstack,
+ gdb_block_iter->name,
+ strlen (gdb_block_iter->name));
+
+ BLOCK_FUNCTION (new_block) = block_name;
+
+ BLOCKVECTOR_BLOCK (symtab->blockvector, i) = new_block;
+ if (begin > BLOCK_START (new_block))
+ begin = BLOCK_START (new_block);
+ if (end < BLOCK_END (new_block))
+ end = BLOCK_END (new_block);
+
+ gdb_block_iter->real_block = new_block;
+ }
+
+ /* Now add the special blocks. */
+ block_iter = NULL;
+ for (i = 0; i < FIRST_LOCAL_BLOCK; i++)
+ {
+ struct block *new_block;
+
+ new_block = (i == GLOBAL_BLOCK
+ ? allocate_global_block (&objfile->objfile_obstack)
+ : allocate_block (&objfile->objfile_obstack));
+ BLOCK_DICT (new_block) = dict_create_linear (&objfile->objfile_obstack,
+ NULL);
+ BLOCK_SUPERBLOCK (new_block) = block_iter;
+ block_iter = new_block;
+
+ BLOCK_START (new_block) = (CORE_ADDR) begin;
+ BLOCK_END (new_block) = (CORE_ADDR) end;
+
+ BLOCKVECTOR_BLOCK (symtab->blockvector, i) = new_block;
+
+ if (i == GLOBAL_BLOCK)
+ set_block_symtab (new_block, symtab);
+ }
+
+ /* Fill up the superblock fields for the real blocks, using the
+ real_block fields populated earlier. */
+ for (gdb_block_iter = stab->blocks;
+ gdb_block_iter;
+ gdb_block_iter = gdb_block_iter->next)
+ {
+ if (gdb_block_iter->parent != NULL)
+ {
+ /* If the plugin specifically mentioned a parent block, we
+ use that. */
+ BLOCK_SUPERBLOCK (gdb_block_iter->real_block) =
+ gdb_block_iter->parent->real_block;
+ }
+ else
+ {
+ /* And if not, we set a default parent block. */
+ BLOCK_SUPERBLOCK (gdb_block_iter->real_block) =
+ BLOCKVECTOR_BLOCK (symtab->blockvector, STATIC_BLOCK);
+ }
+ }
+
+ /* Free memory. */
+ gdb_block_iter = stab->blocks;
+
+ for (gdb_block_iter = stab->blocks, gdb_block_iter_tmp = gdb_block_iter->next;
+ gdb_block_iter;
+ gdb_block_iter = gdb_block_iter_tmp)
+ {
+ xfree ((void *) gdb_block_iter->name);
+ xfree (gdb_block_iter);
+ }
+ xfree (stab->linetable);
+ xfree ((char *) stab->file_name);
+ xfree (stab);
+}
+
+/* Called when closing a gdb_objfile. Converts OBJ to a proper
+ objfile. */
+
+static void
+jit_object_close_impl (struct gdb_symbol_callbacks *cb,
+ struct gdb_object *obj)
+{
+ struct gdb_symtab *i, *j;
+ struct objfile *objfile;
+ jit_dbg_reader_data *priv_data;
+
+ priv_data = cb->priv_data;
+
+ objfile = allocate_objfile (NULL, "<< JIT compiled code >>",
+ OBJF_NOT_FILENAME);
+ objfile->per_bfd->gdbarch = target_gdbarch ();
+
+ terminate_minimal_symbol_table (objfile);
+
+ j = NULL;
+ for (i = obj->symtabs; i; i = j)
+ {
+ j = i->next;
+ finalize_symtab (i, objfile);
+ }
+ add_objfile_entry (objfile, *priv_data);
+ xfree (obj);
+}
+
+/* Try to read CODE_ENTRY using the loaded jit reader (if any).
+ ENTRY_ADDR is the address of the struct jit_code_entry in the
+ inferior address space. */
+
+static int
+jit_reader_try_read_symtab (struct jit_code_entry *code_entry,
+ CORE_ADDR entry_addr)
+{
+ void *gdb_mem;
+ int status;
+ jit_dbg_reader_data priv_data;
+ struct gdb_reader_funcs *funcs;
+ volatile struct gdb_exception e;
+ struct gdb_symbol_callbacks callbacks =
+ {
+ jit_object_open_impl,
+ jit_symtab_open_impl,
+ jit_block_open_impl,
+ jit_symtab_close_impl,
+ jit_object_close_impl,
+
+ jit_symtab_line_mapping_add_impl,
+ jit_target_read_impl,
+
+ &priv_data
+ };
+
+ priv_data = entry_addr;
+
+ if (!loaded_jit_reader)
+ return 0;
+
+ gdb_mem = xmalloc (code_entry->symfile_size);
+
+ status = 1;
+ TRY_CATCH (e, RETURN_MASK_ALL)
+ if (target_read_memory (code_entry->symfile_addr, gdb_mem,
+ code_entry->symfile_size))
+ status = 0;
+ if (e.reason < 0)
+ status = 0;
+
+ if (status)
+ {
+ funcs = loaded_jit_reader->functions;
+ if (funcs->read (funcs, &callbacks, gdb_mem, code_entry->symfile_size)
+ != GDB_SUCCESS)
+ status = 0;
+ }
+
+ xfree (gdb_mem);
+ if (jit_debug && status == 0)
+ fprintf_unfiltered (gdb_stdlog,
+ "Could not read symtab using the loaded JIT reader.\n");
+ return status;
+}
+
+/* Try to read CODE_ENTRY using BFD. ENTRY_ADDR is the address of the
+ struct jit_code_entry in the inferior address space. */
+
+static void
+jit_bfd_try_read_symtab (struct jit_code_entry *code_entry,
+ CORE_ADDR entry_addr,
+ struct gdbarch *gdbarch)
{
bfd *nbfd;
struct section_addr_info *sai;
struct bfd_section *sec;
struct objfile *objfile;
- struct cleanup *old_cleanups, *my_cleanups;
+ struct cleanup *old_cleanups;
int i;
const struct bfd_arch_info *b;
- CORE_ADDR *entry_addr_ptr;
+
+ if (jit_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "jit_register_code, symfile_addr = %s, "
+ "symfile_size = %s\n",
+ paddress (gdbarch, code_entry->symfile_addr),
+ pulongest (code_entry->symfile_size));
nbfd = bfd_open_from_target_memory (code_entry->symfile_addr,
code_entry->symfile_size, gnutarget);
- old_cleanups = make_cleanup_bfd_close (nbfd);
+ if (nbfd == NULL)
+ {
+ puts_unfiltered (_("Error opening JITed symbol file, ignoring it.\n"));
+ return;
+ }
/* Check the format. NOTE: This initializes important data that GDB uses!
We would segfault later without this line. */
{
printf_unfiltered (_("\
JITed symbol file is not an object file, ignoring it.\n"));
- do_cleanups (old_cleanups);
+ gdb_bfd_unref (nbfd);
return;
}
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);
+ old_cleanups = 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)
sai->other[i].sectindex = sec->index;
++i;
}
+ sai->num_sections = i;
+
+ /* This call does not take ownership of SAI. */
+ make_cleanup_bfd_unref (nbfd);
+ objfile = symbol_file_add_from_bfd (nbfd, bfd_get_filename (nbfd), 0, sai,
+ OBJF_SHARED | OBJF_NOT_FILENAME, NULL);
+
+ do_cleanups (old_cleanups);
+ add_objfile_entry (objfile, entry_addr);
+}
- /* 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 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. */
- /* This call takes ownership of sai. */
- objfile = symbol_file_add_from_bfd (nbfd, 0, sai, OBJF_SHARED);
+static void
+jit_register_code (struct gdbarch *gdbarch,
+ CORE_ADDR entry_addr, struct jit_code_entry *code_entry)
+{
+ int success;
- /* Clear the registering_code flag. */
- do_cleanups (my_cleanups);
+ if (jit_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "jit_register_code, symfile_addr = %s, "
+ "symfile_size = %s\n",
+ paddress (gdbarch, code_entry->symfile_addr),
+ pulongest (code_entry->symfile_size));
- /* 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);
+ success = jit_reader_try_read_symtab (code_entry, entry_addr);
- discard_cleanups (old_cleanups);
+ if (!success)
+ jit_bfd_try_read_symtab (code_entry, entry_addr, gdbarch);
}
-/* This function unregisters JITed code and frees the corresponding objfile. */
+/* This function unregisters JITed code and frees the corresponding
+ objfile. */
static void
jit_unregister_code (struct objfile *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)
+ struct jit_objfile_data *objf_data;
+
+ objf_data = objfile_data (objf, jit_objfile_data);
+ if (objf_data != NULL && objf_data->addr == entry_addr)
return objf;
}
return NULL;
}
-/* (Re-)Initialize the jit breakpoint handler, and register any already
- created translations. */
+/* This is called when a breakpoint is deleted. It updates the
+ inferior's cache, if needed. */
static void
-jit_inferior_init (struct gdbarch *gdbarch)
+jit_breakpoint_deleted (struct breakpoint *b)
+{
+ struct bp_location *iter;
+
+ if (b->type != bp_jit_event)
+ return;
+
+ for (iter = b->loc; iter != NULL; iter = iter->next)
+ {
+ struct jit_program_space_data *ps_data;
+
+ ps_data = program_space_data (iter->pspace, jit_program_space_data);
+ if (ps_data != NULL && ps_data->jit_breakpoint == iter->owner)
+ {
+ ps_data->cached_code_address = 0;
+ ps_data->jit_breakpoint = NULL;
+ }
+ }
+}
+
+/* (Re-)Initialize the jit breakpoint if necessary.
+ Return 0 on success. */
+
+static int
+jit_breakpoint_re_set_internal (struct gdbarch *gdbarch,
+ struct jit_program_space_data *ps_data)
{
- struct minimal_symbol *reg_symbol;
+ struct bound_minimal_symbol reg_symbol;
struct minimal_symbol *desc_symbol;
- CORE_ADDR reg_addr;
+ struct jit_objfile_data *objf_data;
+ CORE_ADDR addr;
+
+ if (ps_data->objfile == NULL)
+ {
+ /* Lookup the registration symbol. If it is missing, then we
+ assume we are not attached to a JIT. */
+ reg_symbol = lookup_minimal_symbol_and_objfile (jit_break_name);
+ if (reg_symbol.minsym == NULL
+ || SYMBOL_VALUE_ADDRESS (reg_symbol.minsym) == 0)
+ return 1;
+
+ desc_symbol = lookup_minimal_symbol (jit_descriptor_name, NULL,
+ reg_symbol.objfile);
+ if (desc_symbol == NULL || SYMBOL_VALUE_ADDRESS (desc_symbol) == 0)
+ return 1;
+
+ objf_data = get_jit_objfile_data (reg_symbol.objfile);
+ objf_data->register_code = reg_symbol.minsym;
+ objf_data->descriptor = desc_symbol;
+
+ ps_data->objfile = reg_symbol.objfile;
+ }
+ else
+ objf_data = get_jit_objfile_data (ps_data->objfile);
+
+ addr = SYMBOL_VALUE_ADDRESS (objf_data->register_code);
+
+ if (jit_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "jit_breakpoint_re_set_internal, "
+ "breakpoint_addr = %s\n",
+ paddress (gdbarch, addr));
+
+ if (ps_data->cached_code_address == addr)
+ return 1;
+
+ /* Delete the old breakpoint. */
+ if (ps_data->jit_breakpoint != NULL)
+ delete_breakpoint (ps_data->jit_breakpoint);
+
+ /* Put a breakpoint in the registration symbol. */
+ ps_data->cached_code_address = addr;
+ ps_data->jit_breakpoint = create_jit_event_breakpoint (gdbarch, addr);
+
+ return 0;
+}
+
+/* The private data passed around in the frame unwind callback
+ functions. */
+
+struct jit_unwind_private
+{
+ /* Cached register values. See jit_frame_sniffer to see how this
+ works. */
+ struct gdb_reg_value **registers;
+
+ /* The frame being unwound. */
+ struct frame_info *this_frame;
+};
+
+/* Sets the value of a particular register in this frame. */
+
+static void
+jit_unwind_reg_set_impl (struct gdb_unwind_callbacks *cb, int dwarf_regnum,
+ struct gdb_reg_value *value)
+{
+ struct jit_unwind_private *priv;
+ int gdb_reg;
+
+ priv = cb->priv_data;
+
+ gdb_reg = gdbarch_dwarf2_reg_to_regnum (get_frame_arch (priv->this_frame),
+ dwarf_regnum);
+ if (gdb_reg == -1)
+ {
+ if (jit_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ _("Could not recognize DWARF regnum %d"),
+ dwarf_regnum);
+ return;
+ }
+
+ gdb_assert (priv->registers);
+ priv->registers[gdb_reg] = value;
+}
+
+static void
+reg_value_free_impl (struct gdb_reg_value *value)
+{
+ xfree (value);
+}
+
+/* Get the value of register REGNUM in the previous frame. */
+
+static struct gdb_reg_value *
+jit_unwind_reg_get_impl (struct gdb_unwind_callbacks *cb, int regnum)
+{
+ struct jit_unwind_private *priv;
+ struct gdb_reg_value *value;
+ int gdb_reg, size;
+ struct gdbarch *frame_arch;
+
+ priv = cb->priv_data;
+ frame_arch = get_frame_arch (priv->this_frame);
+
+ gdb_reg = gdbarch_dwarf2_reg_to_regnum (frame_arch, regnum);
+ size = register_size (frame_arch, gdb_reg);
+ value = xmalloc (sizeof (struct gdb_reg_value) + size - 1);
+ value->defined = deprecated_frame_register_read (priv->this_frame, gdb_reg,
+ value->value);
+ value->size = size;
+ value->free = reg_value_free_impl;
+ return value;
+}
+
+/* gdb_reg_value has a free function, which must be called on each
+ saved register value. */
+
+static void
+jit_dealloc_cache (struct frame_info *this_frame, void *cache)
+{
+ struct jit_unwind_private *priv_data = cache;
+ struct gdbarch *frame_arch;
+ int i;
+
+ gdb_assert (priv_data->registers);
+ frame_arch = get_frame_arch (priv_data->this_frame);
+
+ for (i = 0; i < gdbarch_num_regs (frame_arch); i++)
+ if (priv_data->registers[i] && priv_data->registers[i]->free)
+ priv_data->registers[i]->free (priv_data->registers[i]);
+
+ xfree (priv_data->registers);
+ xfree (priv_data);
+}
+
+/* The frame sniffer for the pseudo unwinder.
+
+ While this is nominally a frame sniffer, in the case where the JIT
+ reader actually recognizes the frame, it does a lot more work -- it
+ unwinds the frame and saves the corresponding register values in
+ the cache. jit_frame_prev_register simply returns the saved
+ register values. */
+
+static int
+jit_frame_sniffer (const struct frame_unwind *self,
+ struct frame_info *this_frame, void **cache)
+{
+ struct jit_unwind_private *priv_data;
+ struct gdb_unwind_callbacks callbacks;
+ struct gdb_reader_funcs *funcs;
+
+ callbacks.reg_get = jit_unwind_reg_get_impl;
+ callbacks.reg_set = jit_unwind_reg_set_impl;
+ callbacks.target_read = jit_target_read_impl;
+
+ if (loaded_jit_reader == NULL)
+ return 0;
+
+ funcs = loaded_jit_reader->functions;
+
+ gdb_assert (!*cache);
+
+ *cache = XZALLOC (struct jit_unwind_private);
+ priv_data = *cache;
+ priv_data->registers =
+ XCALLOC (gdbarch_num_regs (get_frame_arch (this_frame)),
+ struct gdb_reg_value *);
+ priv_data->this_frame = this_frame;
+
+ callbacks.priv_data = priv_data;
+
+ /* Try to coax the provided unwinder to unwind the stack */
+ if (funcs->unwind (funcs, &callbacks) == GDB_SUCCESS)
+ {
+ if (jit_debug)
+ fprintf_unfiltered (gdb_stdlog, _("Successfully unwound frame using "
+ "JIT reader.\n"));
+ return 1;
+ }
+ if (jit_debug)
+ fprintf_unfiltered (gdb_stdlog, _("Could not unwind frame using "
+ "JIT reader.\n"));
+
+ jit_dealloc_cache (this_frame, *cache);
+ *cache = NULL;
+
+ return 0;
+}
+
+
+/* The frame_id function for the pseudo unwinder. Relays the call to
+ the loaded plugin. */
+
+static void
+jit_frame_this_id (struct frame_info *this_frame, void **cache,
+ struct frame_id *this_id)
+{
+ struct jit_unwind_private private;
+ struct gdb_frame_id frame_id;
+ struct gdb_reader_funcs *funcs;
+ struct gdb_unwind_callbacks callbacks;
+
+ private.registers = NULL;
+ private.this_frame = this_frame;
+
+ /* We don't expect the frame_id function to set any registers, so we
+ set reg_set to NULL. */
+ callbacks.reg_get = jit_unwind_reg_get_impl;
+ callbacks.reg_set = NULL;
+ callbacks.target_read = jit_target_read_impl;
+ callbacks.priv_data = &private;
+
+ gdb_assert (loaded_jit_reader);
+ funcs = loaded_jit_reader->functions;
+
+ frame_id = funcs->get_frame_id (funcs, &callbacks);
+ *this_id = frame_id_build (frame_id.stack_address, frame_id.code_address);
+}
+
+/* Pseudo unwinder function. Reads the previously fetched value for
+ the register from the cache. */
+
+static struct value *
+jit_frame_prev_register (struct frame_info *this_frame, void **cache, int reg)
+{
+ struct jit_unwind_private *priv = *cache;
+ struct gdb_reg_value *value;
+
+ if (priv == NULL)
+ return frame_unwind_got_optimized (this_frame, reg);
+
+ gdb_assert (priv->registers);
+ value = priv->registers[reg];
+ if (value && value->defined)
+ return frame_unwind_got_bytes (this_frame, reg, value->value);
+ else
+ return frame_unwind_got_optimized (this_frame, reg);
+}
+
+/* Relay everything back to the unwinder registered by the JIT debug
+ info reader.*/
+
+static const struct frame_unwind jit_frame_unwind =
+{
+ NORMAL_FRAME,
+ default_frame_unwind_stop_reason,
+ jit_frame_this_id,
+ jit_frame_prev_register,
+ NULL,
+ jit_frame_sniffer,
+ jit_dealloc_cache
+};
+
+
+/* This is the information that is stored at jit_gdbarch_data for each
+ architecture. */
+
+struct jit_gdbarch_data_type
+{
+ /* Has the (pseudo) unwinder been prepended? */
+ int unwinder_registered;
+};
+
+/* Check GDBARCH and prepend the pseudo JIT unwinder if needed. */
+
+static void
+jit_prepend_unwinder (struct gdbarch *gdbarch)
+{
+ struct jit_gdbarch_data_type *data;
+
+ data = gdbarch_data (gdbarch, jit_gdbarch_data);
+ if (!data->unwinder_registered)
+ {
+ frame_unwind_prepend_unwinder (gdbarch, &jit_frame_unwind);
+ data->unwinder_registered = 1;
+ }
+}
+
+/* Register any already created translations. */
+
+static void
+jit_inferior_init (struct gdbarch *gdbarch)
+{
struct jit_descriptor descriptor;
struct jit_code_entry cur_entry;
+ struct jit_program_space_data *ps_data;
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;
+ if (jit_debug)
+ fprintf_unfiltered (gdb_stdlog, "jit_inferior_init\n");
- /* 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;
+ jit_prepend_unwinder (gdbarch);
- /* 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)
+ ps_data = get_jit_program_space_data ();
+ if (jit_breakpoint_re_set_internal (gdbarch, ps_data) != 0)
return;
- /* Read the descriptor so we can check the version number and load any already
- JITed functions. */
- jit_read_descriptor (gdbarch, &descriptor);
+ /* Read the descriptor so we can check the version number and load
+ any already JITed functions. */
+ if (!jit_read_descriptor (gdbarch, &descriptor, ps_data))
+ return;
/* 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 (gdbarch, reg_addr);
+ {
+ printf_unfiltered (_("Unsupported JIT protocol version %ld "
+ "in descriptor (expected 1)\n"),
+ (long) descriptor.version);
+ return;
+ }
- /* If we've attached to a running program, we need to check the descriptor to
- register any functions that were already generated. */
+ /* 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)
void
jit_inferior_created_hook (void)
{
- jit_inferior_init (target_gdbarch);
+ jit_inferior_init (target_gdbarch ());
}
/* Exported routine to call to re-set the jit breakpoints,
void
jit_breakpoint_re_set (void)
{
- jit_inferior_init (target_gdbarch);
-}
-
-/* Wrapper to match the observer function pointer prototype. */
-
-static void
-jit_inferior_created_observer (struct target_ops *objfile, int from_tty)
-{
- jit_inferior_init (target_gdbarch);
+ jit_breakpoint_re_set_internal (target_gdbarch (),
+ get_jit_program_space_data ());
}
-/* 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. */
+/* 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 (struct inferior *inf)
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);
+ {
+ struct jit_objfile_data *objf_data = objfile_data (objf,
+ jit_objfile_data);
+
+ if (objf_data != NULL && objf_data->addr != 0)
+ jit_unregister_code (objf);
+ }
}
void
struct objfile *objf;
/* Read the descriptor from remote memory. */
- jit_read_descriptor (gdbarch, &descriptor);
+ if (!jit_read_descriptor (gdbarch, &descriptor,
+ get_jit_program_space_data ()))
+ return;
entry_addr = descriptor.relevant_entry;
- /* Do the corresponding action. */
+ /* Do the corresponding action. */
switch (descriptor.action_flag)
{
case JIT_NOACTION:
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: %s\n"),
+ printf_unfiltered (_("Unable to find JITed code "
+ "entry at address: %s\n"),
paddress (gdbarch, entry_addr));
else
jit_unregister_code (objf);
}
}
+/* Called to free the data allocated to the jit_program_space_data slot. */
+
+static void
+free_objfile_data (struct objfile *objfile, void *data)
+{
+ struct jit_objfile_data *objf_data = data;
+
+ if (objf_data->register_code != NULL)
+ {
+ struct jit_program_space_data *ps_data;
+
+ ps_data = program_space_data (objfile->pspace, jit_program_space_data);
+ if (ps_data != NULL && ps_data->objfile == objfile)
+ ps_data->objfile = NULL;
+ }
+
+ xfree (data);
+}
+
+/* Initialize the jit_gdbarch_data slot with an instance of struct
+ jit_gdbarch_data_type */
+
+static void *
+jit_gdbarch_data_init (struct obstack *obstack)
+{
+ struct jit_gdbarch_data_type *data;
+
+ data = obstack_alloc (obstack, sizeof (struct jit_gdbarch_data_type));
+ data->unwinder_registered = 0;
+ return data;
+}
+
/* Provide a prototype to silence -Wmissing-prototypes. */
extern void _initialize_jit (void);
void
_initialize_jit (void)
{
- observer_attach_inferior_created (jit_inferior_created_observer);
+ jit_reader_dir = relocate_gdb_directory (JIT_READER_DIR,
+ JIT_READER_DIR_RELOCATABLE);
+ add_setshow_zuinteger_cmd ("jit", class_maintenance, &jit_debug,
+ _("Set JIT debugging."),
+ _("Show JIT debugging."),
+ _("When non-zero, JIT debugging is enabled."),
+ NULL,
+ show_jit_debug,
+ &setdebuglist, &showdebuglist);
+
observer_attach_inferior_exit (jit_inferior_exit_hook);
- jit_objfile_data = register_objfile_data ();
+ observer_attach_breakpoint_deleted (jit_breakpoint_deleted);
+
+ jit_objfile_data =
+ register_objfile_data_with_cleanup (NULL, free_objfile_data);
+ jit_program_space_data =
+ register_program_space_data_with_cleanup (NULL,
+ jit_program_space_data_cleanup);
+ jit_gdbarch_data = gdbarch_data_register_pre_init (jit_gdbarch_data_init);
+ if (is_dl_available ())
+ {
+ add_com ("jit-reader-load", no_class, jit_reader_load_command, _("\
+Load FILE as debug info reader and unwinder for JIT compiled code.\n\
+Usage: jit-reader-load FILE\n\
+Try to load file FILE as a debug info reader (and unwinder) for\n\
+JIT compiled code. The file is loaded from " JIT_READER_DIR ",\n\
+relocated relative to the GDB executable if required."));
+ add_com ("jit-reader-unload", no_class, jit_reader_unload_command, _("\
+Unload the currently loaded JIT debug info reader.\n\
+Usage: jit-reader-unload FILE\n\n\
+Do \"help jit-reader-load\" for info on loading debug info readers."));
+ }
}