X-Git-Url: http://drtracing.org/?a=blobdiff_plain;f=gdb%2Fjit.c;h=9fd5ae675b0128e5f2d8ba07147c370361f99b46;hb=b8162e5ac9e052b2a88912b729081600972e854c;hp=42ef8efb5cad4ecb01f0c3eb06fb178ec2891688;hpb=c6913b7d1e1863c208fb6495b227ef5ea119243e;p=deliverable%2Fbinutils-gdb.git diff --git a/gdb/jit.c b/gdb/jit.c index 42ef8efb5c..9fd5ae675b 100644 --- a/gdb/jit.c +++ b/gdb/jit.c @@ -1,6 +1,6 @@ /* Handle JIT code generation in the inferior for GDB, the GNU Debugger. - Copyright (C) 2009, 2010 Free Software Foundation, Inc. + Copyright (C) 2009-2016 Free Software Foundation, Inc. This file is part of GDB. @@ -20,14 +20,27 @@ #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_stat.h" +#include "gdb-dlfcn.h" +#include +#include "gdb_bfd.h" + +static const char *jit_reader_dir = NULL; static const struct objfile_data *jit_objfile_data; @@ -35,31 +48,31 @@ 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 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. */ @@ -76,7 +89,9 @@ static int 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 @@ -111,6 +126,7 @@ mem_bfd_iovec_stat (struct bfd *abfd, void *stream, struct stat *sb) { struct target_buffer *buffer = (struct target_buffer*) stream; + memset (sb, 0, sizeof (struct stat)); sb->st_size = buffer->size; return 0; } @@ -118,26 +134,213 @@ mem_bfd_iovec_stat (struct bfd *abfd, void *stream, struct stat *sb) /* 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 (""); - struct target_buffer *buffer = xmalloc (sizeof (struct target_buffer)); + struct target_buffer *buffer = XNEW (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 ("", target, + mem_bfd_iovec_open, + buffer, + mem_bfd_iovec_pread, + mem_bfd_iovec_close, + mem_bfd_iovec_stat); +} + +/* 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 = (reader_init_fn_type *) 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 = XCNEW (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", jit_reader_dir, SLASH_STRING, 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 = (struct jit_objfile_data *) objfile_data (objf, jit_objfile_data); + if (objf_data == NULL) + { + objf_data = XCNEW (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; } -/* Helper function for reading the global JIT descriptor from remote memory. */ +/* 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 + = ((struct jit_program_space_data *) + program_space_data (current_program_space, jit_program_space_data)); + if (ps_data == NULL) + { + ps_data = XCNEW (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; @@ -145,17 +348,36 @@ jit_read_descriptor (struct gdbarch *gdbarch, 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, MSYMBOL_VALUE_ADDRESS (ps_data->objfile, + 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; ptr_size = TYPE_LENGTH (ptr_type); desc_size = 8 + 2 * ptr_size; /* Two 32-bit ints and two pointers. */ - desc_buf = alloca (desc_size); + desc_buf = (gdb_byte *) alloca (desc_size); /* Read the descriptor. */ - err = target_read_memory (jit_descriptor_addr, desc_buf, desc_size); + err = target_read_memory (MSYMBOL_VALUE_ADDRESS (ps_data->objfile, + 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); @@ -164,6 +386,8 @@ jit_read_descriptor (struct gdbarch *gdbarch, 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. */ @@ -172,18 +396,25 @@ static void 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. */ - entry_buf = alloca (entry_size); + + /* 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 = (gdb_byte *) alloca (entry_size); /* Read the entry. */ err = target_read_memory (code_addr, entry_buf, entry_size); @@ -198,30 +429,480 @@ jit_read_code_entry (struct gdbarch *gdbarch, 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_byte *) 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 XCNEW (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 = XCNEW (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 newobj) +{ + if (old == NULL) + return 1; + if (old->begin < newobj->begin) + return 1; + else if (old->begin == newobj->begin) + { + if (old->end > newobj->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 = XCNEW (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; + int alloc_len; + + if (nlines < 1) + return; + + alloc_len = sizeof (struct linetable) + + (nlines - 1) * sizeof (struct linetable_entry); + stab->linetable = (struct linetable *) xmalloc (alloc_len); + 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 compunit_symtab *cust; + struct gdb_block *gdb_block_iter, *gdb_block_iter_tmp; + struct block *block_iter; + int actual_nblocks, i; + size_t blockvector_size; + CORE_ADDR begin, end; + struct blockvector *bv; + + actual_nblocks = FIRST_LOCAL_BLOCK + stab->nblocks; + + cust = allocate_compunit_symtab (objfile, stab->file_name); + allocate_symtab (cust, stab->file_name); + add_compunit_symtab_to_objfile (cust); + + /* JIT compilers compile in memory. */ + COMPUNIT_DIRNAME (cust) = NULL; + + /* Copy over the linetable entry if one was provided. */ + if (stab->linetable) + { + size_t size = ((stab->linetable->nitems - 1) + * sizeof (struct linetable_entry) + + sizeof (struct linetable)); + SYMTAB_LINETABLE (COMPUNIT_FILETABS (cust)) + = (struct linetable *) obstack_alloc (&objfile->objfile_obstack, size); + memcpy (SYMTAB_LINETABLE (COMPUNIT_FILETABS (cust)), stab->linetable, + size); + } + + blockvector_size = (sizeof (struct blockvector) + + (actual_nblocks - 1) * sizeof (struct block *)); + bv = (struct blockvector *) obstack_alloc (&objfile->objfile_obstack, + blockvector_size); + COMPUNIT_BLOCKVECTOR (cust) = bv; + + /* (begin, end) will contain the PC range this entire blockvector + spans. */ + BLOCKVECTOR_MAP (bv) = NULL; + begin = stab->blocks->begin; + end = stab->blocks->end; + BLOCKVECTOR_NBLOCKS (bv) = 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_set_symtab (block_name, COMPUNIT_FILETABS (cust)); + SYMBOL_TYPE (block_name) = lookup_function_type (block_type); + SYMBOL_BLOCK_VALUE (block_name) = new_block; + + block_name->ginfo.name + = (const char *) obstack_copy0 (&objfile->objfile_obstack, + gdb_block_iter->name, + strlen (gdb_block_iter->name)); + + BLOCK_FUNCTION (new_block) = block_name; + + BLOCKVECTOR_BLOCK (bv, 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 (bv, i) = new_block; + + if (i == GLOBAL_BLOCK) + set_block_compunit_symtab (new_block, cust); + } + + /* 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 (bv, 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 = (jit_dbg_reader_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) +{ + gdb_byte *gdb_mem; + int status; + jit_dbg_reader_data priv_data; + struct gdb_reader_funcs *funcs; + 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 = (gdb_byte *) xmalloc (code_entry->symfile_size); + + status = 1; + TRY + { + if (target_read_memory (code_entry->symfile_addr, gdb_mem, + code_entry->symfile_size)) + status = 0; + } + CATCH (e, RETURN_MASK_ALL) + { + status = 0; + } + END_CATCH + + 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. */ @@ -229,7 +910,7 @@ jit_register_code (struct gdbarch *gdbarch, { printf_unfiltered (_("\ JITed symbol file is not an object file, ignoring it.\n")); - do_cleanups (old_cleanups); + gdb_bfd_unref (nbfd); return; } @@ -244,7 +925,7 @@ JITed symbol file is not an object file, ignoring it.\n")); 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) @@ -256,27 +937,43 @@ JITed symbol file is not an object file, ignoring it.\n")); sai->other[i].sectindex = sec->index; ++i; } + sai->num_sections = 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 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); +} + +/* 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) @@ -290,67 +987,371 @@ 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) + struct jit_objfile_data *objf_data; + + objf_data + = (struct jit_objfile_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_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 = ((struct jit_program_space_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 if the jit breakpoint has been successfully initialized. */ + +static int +jit_breakpoint_re_set_internal (struct gdbarch *gdbarch, + struct jit_program_space_data *ps_data) +{ + struct bound_minimal_symbol reg_symbol; + struct bound_minimal_symbol desc_symbol; + 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 + || BMSYMBOL_VALUE_ADDRESS (reg_symbol) == 0) + return 1; + + desc_symbol = lookup_minimal_symbol (jit_descriptor_name, NULL, + reg_symbol.objfile); + if (desc_symbol.minsym == NULL + || BMSYMBOL_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.minsym; + + ps_data->objfile = reg_symbol.objfile; + } + else + objf_data = get_jit_objfile_data (ps_data->objfile); + + addr = MSYMBOL_VALUE_ADDRESS (ps_data->objfile, 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 0; + + /* 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 = (struct jit_unwind_private *) 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 = (struct jit_unwind_private *) 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 = ((struct 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 = (struct jit_unwind_private *) 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 = XCNEW (struct jit_unwind_private); + priv_data = (struct jit_unwind_private *) *cache; + priv_data->registers = + XCNEWVEC (struct gdb_reg_value *, + gdbarch_num_regs (get_frame_arch (this_frame))); + 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 priv; + struct gdb_frame_id frame_id; + struct gdb_reader_funcs *funcs; + struct gdb_unwind_callbacks callbacks; + + priv.registers = NULL; + priv.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 = &priv; + + 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 = (struct jit_unwind_private *) *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 + = (struct jit_gdbarch_data_type *) 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 minimal_symbol *reg_symbol; - struct minimal_symbol *desc_symbol; - CORE_ADDR reg_addr; struct jit_descriptor descriptor; struct jit_code_entry cur_entry; + struct jit_program_space_data *ps_data; CORE_ADDR cur_entry_addr; - /* 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) @@ -366,12 +1367,20 @@ jit_inferior_init (struct gdbarch *gdbarch) } } +/* inferior_created observer. */ + +static void +jit_inferior_created (struct target_ops *ops, int from_tty) +{ + jit_inferior_created_hook (); +} + /* Exported routine to call when an inferior has been created. */ 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, @@ -380,20 +1389,13 @@ jit_inferior_created_hook (void) 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) @@ -401,13 +1403,14 @@ 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 + = (struct jit_objfile_data *) objfile_data (objf, jit_objfile_data); + + if (objf_data != NULL && objf_data->addr != 0) + jit_unregister_code (objf); + } } void @@ -419,10 +1422,12 @@ jit_event_handler (struct gdbarch *gdbarch) 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: @@ -434,7 +1439,8 @@ jit_event_handler (struct gdbarch *gdbarch) 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); @@ -446,6 +1452,41 @@ jit_event_handler (struct gdbarch *gdbarch) } } +/* 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 = (struct jit_objfile_data *) data; + + if (objf_data->register_code != NULL) + { + struct jit_program_space_data *ps_data; + + ps_data + = ((struct jit_program_space_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 = + XOBNEW (obstack, struct jit_gdbarch_data_type); + + data->unwinder_registered = 0; + + return data; +} + /* Provide a prototype to silence -Wmissing-prototypes. */ extern void _initialize_jit (void); @@ -453,7 +1494,37 @@ 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_created (jit_inferior_created); 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.")); + } }