#include "defs.h"
#include "gdb_string.h"
#include "inferior.h"
#include "gdbcore.h"
#include "solist.h"
#include "frv-tdep.h"
#include "objfiles.h"
#include "symtab.h"
#include "language.h"
#include "command.h"
#include "gdbcmd.h"
#include "elf/frv.h"
static int solib_frv_debug;
enum { FRV_PTR_SIZE = 4 };
typedef unsigned char ext_Elf32_Half[2];
typedef unsigned char ext_Elf32_Addr[4];
typedef unsigned char ext_Elf32_Word[4];
struct ext_elf32_fdpic_loadseg
{
ext_Elf32_Addr addr;
ext_Elf32_Addr p_vaddr;
ext_Elf32_Word p_memsz;
};
struct ext_elf32_fdpic_loadmap {
ext_Elf32_Half version;
ext_Elf32_Half nsegs;
struct ext_elf32_fdpic_loadseg segs[1 ];
};
struct int_elf32_fdpic_loadseg
{
CORE_ADDR addr;
CORE_ADDR p_vaddr;
long p_memsz;
};
struct int_elf32_fdpic_loadmap {
int version;
int nsegs;
struct int_elf32_fdpic_loadseg segs[1 ];
};
static struct int_elf32_fdpic_loadmap *
fetch_loadmap (CORE_ADDR ldmaddr)
{
struct ext_elf32_fdpic_loadmap ext_ldmbuf_partial;
struct ext_elf32_fdpic_loadmap *ext_ldmbuf;
struct int_elf32_fdpic_loadmap *int_ldmbuf;
int ext_ldmbuf_size, int_ldmbuf_size;
int version, seg, nsegs;
if (target_read_memory (ldmaddr, (char *) &ext_ldmbuf_partial,
sizeof ext_ldmbuf_partial))
{
return NULL;
}
version = extract_unsigned_integer (&ext_ldmbuf_partial.version,
sizeof ext_ldmbuf_partial.version);
if (version != 0)
{
return NULL;
}
nsegs = extract_unsigned_integer (&ext_ldmbuf_partial.nsegs,
sizeof ext_ldmbuf_partial.nsegs);
ext_ldmbuf_size = sizeof (struct ext_elf32_fdpic_loadmap)
+ (nsegs - 1) * sizeof (struct ext_elf32_fdpic_loadseg);
ext_ldmbuf = xmalloc (ext_ldmbuf_size);
memcpy (ext_ldmbuf, &ext_ldmbuf_partial, sizeof ext_ldmbuf_partial);
if (target_read_memory (ldmaddr + sizeof ext_ldmbuf_partial,
(char *) ext_ldmbuf + sizeof ext_ldmbuf_partial,
ext_ldmbuf_size - sizeof ext_ldmbuf_partial))
{
xfree (ext_ldmbuf);
return NULL;
}
int_ldmbuf_size = sizeof (struct int_elf32_fdpic_loadmap)
+ (nsegs - 1) * sizeof (struct int_elf32_fdpic_loadseg);
int_ldmbuf = xmalloc (int_ldmbuf_size);
int_ldmbuf->version = version;
int_ldmbuf->nsegs = nsegs;
for (seg = 0; seg < nsegs; seg++)
{
int_ldmbuf->segs[seg].addr
= extract_unsigned_integer (&ext_ldmbuf->segs[seg].addr,
sizeof (ext_ldmbuf->segs[seg].addr));
int_ldmbuf->segs[seg].p_vaddr
= extract_unsigned_integer (&ext_ldmbuf->segs[seg].p_vaddr,
sizeof (ext_ldmbuf->segs[seg].p_vaddr));
int_ldmbuf->segs[seg].p_memsz
= extract_unsigned_integer (&ext_ldmbuf->segs[seg].p_memsz,
sizeof (ext_ldmbuf->segs[seg].p_memsz));
}
xfree (ext_ldmbuf);
return int_ldmbuf;
}
typedef unsigned char ext_ptr[4];
struct ext_elf32_fdpic_loadaddr
{
ext_ptr map;
ext_ptr got_value;
};
struct ext_link_map
{
struct ext_elf32_fdpic_loadaddr l_addr;
ext_ptr l_name;
ext_ptr l_ld;
ext_ptr l_next, l_prev;
};
struct lm_info
{
struct int_elf32_fdpic_loadmap *map;
CORE_ADDR got_value;
CORE_ADDR lm_addr;
asymbol **dyn_syms;
arelent **dyn_relocs;
int dyn_reloc_count;
};
static struct lm_info *main_executable_lm_info;
static void frv_relocate_main_executable (void);
static CORE_ADDR main_got (void);
static int enable_break2 (void);
static CORE_ADDR
bfd_lookup_symbol (bfd *abfd, char *symname)
{
long storage_needed;
asymbol *sym;
asymbol **symbol_table;
unsigned int number_of_symbols;
unsigned int i;
struct cleanup *back_to;
CORE_ADDR symaddr = 0;
storage_needed = bfd_get_symtab_upper_bound (abfd);
if (storage_needed > 0)
{
symbol_table = (asymbol **) xmalloc (storage_needed);
back_to = make_cleanup (xfree, symbol_table);
number_of_symbols = bfd_canonicalize_symtab (abfd, symbol_table);
for (i = 0; i < number_of_symbols; i++)
{
sym = *symbol_table++;
if (strcmp (sym->name, symname) == 0)
{
symaddr = sym->value + sym->section->vma;
break;
}
}
do_cleanups (back_to);
}
if (symaddr)
return symaddr;
storage_needed = bfd_get_dynamic_symtab_upper_bound (abfd);
if (storage_needed > 0)
{
symbol_table = (asymbol **) xmalloc (storage_needed);
back_to = make_cleanup (xfree, symbol_table);
number_of_symbols = bfd_canonicalize_dynamic_symtab (abfd, symbol_table);
for (i = 0; i < number_of_symbols; i++)
{
sym = *symbol_table++;
if (strcmp (sym->name, symname) == 0)
{
symaddr = sym->value + sym->section->vma;
break;
}
}
do_cleanups (back_to);
}
return symaddr;
}
static int
open_symbol_file_object (void *from_ttyp)
{
return 0;
}
static CORE_ADDR lm_base_cache = 0;
static CORE_ADDR main_lm_addr = 0;
static CORE_ADDR
lm_base (void)
{
struct minimal_symbol *got_sym;
CORE_ADDR addr;
char buf[FRV_PTR_SIZE];
if (lm_base_cache)
return lm_base_cache;
got_sym = lookup_minimal_symbol ("_GLOBAL_OFFSET_TABLE_", NULL,
symfile_objfile);
if (got_sym == 0)
{
if (solib_frv_debug)
fprintf_unfiltered (gdb_stdlog,
"lm_base: _GLOBAL_OFFSET_TABLE_ not found.\n");
return 0;
}
addr = SYMBOL_VALUE_ADDRESS (got_sym) + 8;
if (solib_frv_debug)
fprintf_unfiltered (gdb_stdlog,
"lm_base: _GLOBAL_OFFSET_TABLE_ + 8 = %s\n",
hex_string_custom (addr, 8));
if (target_read_memory (addr, buf, sizeof buf) != 0)
return 0;
lm_base_cache = extract_unsigned_integer (buf, sizeof buf);
if (solib_frv_debug)
fprintf_unfiltered (gdb_stdlog,
"lm_base: lm_base_cache = %s\n",
hex_string_custom (lm_base_cache, 8));
return lm_base_cache;
}
static struct so_list *
frv_current_sos (void)
{
CORE_ADDR lm_addr, mgot;
struct so_list *sos_head = NULL;
struct so_list **sos_next_ptr = &sos_head;
mgot = main_got ();
lm_addr = lm_base ();
while (lm_addr)
{
struct ext_link_map lm_buf;
CORE_ADDR got_addr;
if (solib_frv_debug)
fprintf_unfiltered (gdb_stdlog,
"current_sos: reading link_map entry at %s\n",
hex_string_custom (lm_addr, 8));
if (target_read_memory (lm_addr, (char *) &lm_buf, sizeof (lm_buf)) != 0)
{
warning (_("frv_current_sos: Unable to read link map entry. Shared object chain may be incomplete."));
break;
}
got_addr
= extract_unsigned_integer (&lm_buf.l_addr.got_value,
sizeof (lm_buf.l_addr.got_value));
if (got_addr != mgot)
{
int errcode;
char *name_buf;
struct int_elf32_fdpic_loadmap *loadmap;
struct so_list *sop;
CORE_ADDR addr;
addr = extract_unsigned_integer (&lm_buf.l_addr.map,
sizeof lm_buf.l_addr.map);
loadmap = fetch_loadmap (addr);
if (loadmap == NULL)
{
warning (_("frv_current_sos: Unable to fetch load map. Shared object chain may be incomplete."));
break;
}
sop = xcalloc (1, sizeof (struct so_list));
sop->lm_info = xcalloc (1, sizeof (struct lm_info));
sop->lm_info->map = loadmap;
sop->lm_info->got_value = got_addr;
sop->lm_info->lm_addr = lm_addr;
addr = extract_unsigned_integer (&lm_buf.l_name,
sizeof (lm_buf.l_name));
target_read_string (addr, &name_buf, SO_NAME_MAX_PATH_SIZE - 1,
&errcode);
if (solib_frv_debug)
fprintf_unfiltered (gdb_stdlog, "current_sos: name = %s\n",
name_buf);
if (errcode != 0)
warning (_("Can't read pathname for link map entry: %s."),
safe_strerror (errcode));
else
{
strncpy (sop->so_name, name_buf, SO_NAME_MAX_PATH_SIZE - 1);
sop->so_name[SO_NAME_MAX_PATH_SIZE - 1] = '\0';
xfree (name_buf);
strcpy (sop->so_original_name, sop->so_name);
}
*sos_next_ptr = sop;
sos_next_ptr = &sop->next;
}
else
{
main_lm_addr = lm_addr;
}
lm_addr = extract_unsigned_integer (&lm_buf.l_next, sizeof (lm_buf.l_next));
}
enable_break2 ();
return sos_head;
}
static CORE_ADDR interp_text_sect_low;
static CORE_ADDR interp_text_sect_high;
static CORE_ADDR interp_plt_sect_low;
static CORE_ADDR interp_plt_sect_high;
static int
frv_in_dynsym_resolve_code (CORE_ADDR pc)
{
return ((pc >= interp_text_sect_low && pc < interp_text_sect_high)
|| (pc >= interp_plt_sect_low && pc < interp_plt_sect_high)
|| in_plt_section (pc, NULL));
}
CORE_ADDR
displacement_from_map (struct int_elf32_fdpic_loadmap *map,
CORE_ADDR addr)
{
int seg;
for (seg = 0; seg < map->nsegs; seg++)
{
if (map->segs[seg].p_vaddr <= addr
&& addr < map->segs[seg].p_vaddr + map->segs[seg].p_memsz)
{
return map->segs[seg].addr - map->segs[seg].p_vaddr;
}
}
return 0;
}
static void
enable_break_failure_warning (void)
{
warning (_("Unable to find dynamic linker breakpoint function.\n"
"GDB will be unable to debug shared library initializers\n"
"and track explicitly loaded dynamic code."));
}
static int enable_break1_done = 0;
static int enable_break2_done = 0;
static int
enable_break2 (void)
{
int success = 0;
char **bkpt_namep;
asection *interp_sect;
if (!enable_break1_done || enable_break2_done)
return 1;
enable_break2_done = 1;
remove_solib_event_breakpoints ();
interp_text_sect_low = interp_text_sect_high = 0;
interp_plt_sect_low = interp_plt_sect_high = 0;
interp_sect = bfd_get_section_by_name (exec_bfd, ".interp");
if (interp_sect)
{
unsigned int interp_sect_size;
char *buf;
bfd *tmp_bfd = NULL;
int tmp_fd = -1;
char *tmp_pathname = NULL;
int status;
CORE_ADDR addr, interp_loadmap_addr;
char addr_buf[FRV_PTR_SIZE];
struct int_elf32_fdpic_loadmap *ldm;
interp_sect_size = bfd_section_size (exec_bfd, interp_sect);
buf = alloca (interp_sect_size);
bfd_get_section_contents (exec_bfd, interp_sect,
buf, 0, interp_sect_size);
tmp_fd = solib_open (buf, &tmp_pathname);
if (tmp_fd >= 0)
tmp_bfd = bfd_fopen (tmp_pathname, gnutarget, FOPEN_RB, tmp_fd);
if (tmp_bfd == NULL)
{
enable_break_failure_warning ();
return 0;
}
if (!bfd_check_format (tmp_bfd, bfd_object))
{
warning (_("Unable to grok dynamic linker %s as an object file"), buf);
enable_break_failure_warning ();
bfd_close (tmp_bfd);
return 0;
}
status = frv_fdpic_loadmap_addresses (current_gdbarch,
&interp_loadmap_addr, 0);
if (status < 0)
{
warning (_("Unable to determine dynamic linker loadmap address."));
enable_break_failure_warning ();
bfd_close (tmp_bfd);
return 0;
}
if (solib_frv_debug)
fprintf_unfiltered (gdb_stdlog,
"enable_break: interp_loadmap_addr = %s\n",
hex_string_custom (interp_loadmap_addr, 8));
ldm = fetch_loadmap (interp_loadmap_addr);
if (ldm == NULL)
{
warning (_("Unable to load dynamic linker loadmap at address %s."),
hex_string_custom (interp_loadmap_addr, 8));
enable_break_failure_warning ();
bfd_close (tmp_bfd);
return 0;
}
interp_sect = bfd_get_section_by_name (tmp_bfd, ".text");
if (interp_sect)
{
interp_text_sect_low
= bfd_section_vma (tmp_bfd, interp_sect);
interp_text_sect_low
+= displacement_from_map (ldm, interp_text_sect_low);
interp_text_sect_high
= interp_text_sect_low + bfd_section_size (tmp_bfd, interp_sect);
}
interp_sect = bfd_get_section_by_name (tmp_bfd, ".plt");
if (interp_sect)
{
interp_plt_sect_low =
bfd_section_vma (tmp_bfd, interp_sect);
interp_plt_sect_low
+= displacement_from_map (ldm, interp_plt_sect_low);
interp_plt_sect_high =
interp_plt_sect_low + bfd_section_size (tmp_bfd, interp_sect);
}
addr = bfd_lookup_symbol (tmp_bfd, "_dl_debug_addr");
if (addr == 0)
{
warning (_("Could not find symbol _dl_debug_addr in dynamic linker"));
enable_break_failure_warning ();
bfd_close (tmp_bfd);
return 0;
}
if (solib_frv_debug)
fprintf_unfiltered (gdb_stdlog,
"enable_break: _dl_debug_addr (prior to relocation) = %s\n",
hex_string_custom (addr, 8));
addr += displacement_from_map (ldm, addr);
if (solib_frv_debug)
fprintf_unfiltered (gdb_stdlog,
"enable_break: _dl_debug_addr (after relocation) = %s\n",
hex_string_custom (addr, 8));
if (target_read_memory (addr, addr_buf, sizeof addr_buf) != 0)
{
warning (_("Unable to fetch contents of _dl_debug_addr (at address %s) from dynamic linker"),
hex_string_custom (addr, 8));
}
addr = extract_unsigned_integer (addr_buf, sizeof addr_buf);
if (target_read_memory (addr + 8, addr_buf, sizeof addr_buf) != 0)
{
warning (_("Unable to fetch _dl_debug_addr->r_brk (at address %s) from dynamic linker"),
hex_string_custom (addr + 8, 8));
enable_break_failure_warning ();
bfd_close (tmp_bfd);
return 0;
}
addr = extract_unsigned_integer (addr_buf, sizeof addr_buf);
if (target_read_memory (addr, addr_buf, sizeof addr_buf) != 0)
{
warning (_("Unable to fetch _dl_debug_addr->.r_brk entry point (at address %s) from dynamic linker"),
hex_string_custom (addr, 8));
enable_break_failure_warning ();
bfd_close (tmp_bfd);
return 0;
}
addr = extract_unsigned_integer (addr_buf, sizeof addr_buf);
bfd_close (tmp_bfd);
xfree (ldm);
create_solib_event_breakpoint (addr);
return 1;
}
enable_break_failure_warning ();
return 0;
}
static int
enable_break (void)
{
asection *interp_sect;
remove_solib_event_breakpoints ();
interp_sect = bfd_get_section_by_name (exec_bfd, ".interp");
if (interp_sect)
{
enable_break1_done = 1;
create_solib_event_breakpoint (symfile_objfile->ei.entry_point);
if (solib_frv_debug)
fprintf_unfiltered (gdb_stdlog,
"enable_break: solib event breakpoint placed at entry point: %s\n",
hex_string_custom
(symfile_objfile->ei.entry_point, 8));
}
else
{
if (solib_frv_debug)
fprintf_unfiltered (gdb_stdlog,
"enable_break: No .interp section found.\n");
}
return 1;
}
static void
frv_special_symbol_handling (void)
{
}
static void
frv_relocate_main_executable (void)
{
int status;
CORE_ADDR exec_addr;
struct int_elf32_fdpic_loadmap *ldm;
struct cleanup *old_chain;
struct section_offsets *new_offsets;
int changed;
struct obj_section *osect;
status = frv_fdpic_loadmap_addresses (current_gdbarch, 0, &exec_addr);
if (status < 0)
{
return;
}
ldm = fetch_loadmap (exec_addr);
if (ldm == NULL)
error (_("Unable to load the executable's loadmap."));
if (main_executable_lm_info)
xfree (main_executable_lm_info);
main_executable_lm_info = xcalloc (1, sizeof (struct lm_info));
main_executable_lm_info->map = ldm;
new_offsets = xcalloc (symfile_objfile->num_sections,
sizeof (struct section_offsets));
old_chain = make_cleanup (xfree, new_offsets);
changed = 0;
ALL_OBJFILE_OSECTIONS (symfile_objfile, osect)
{
CORE_ADDR orig_addr, addr, offset;
int osect_idx;
int seg;
osect_idx = osect->the_bfd_section->index;
addr = osect->addr;
offset = ANOFFSET (symfile_objfile->section_offsets, osect_idx);
orig_addr = addr - offset;
for (seg = 0; seg < ldm->nsegs; seg++)
{
if (ldm->segs[seg].p_vaddr <= orig_addr
&& orig_addr < ldm->segs[seg].p_vaddr + ldm->segs[seg].p_memsz)
{
new_offsets->offsets[osect_idx]
= ldm->segs[seg].addr - ldm->segs[seg].p_vaddr;
if (new_offsets->offsets[osect_idx] != offset)
changed = 1;
break;
}
}
}
if (changed)
objfile_relocate (symfile_objfile, new_offsets);
do_cleanups (old_chain);
main_executable_lm_info->got_value = main_got ();
}
static void
frv_solib_create_inferior_hook (void)
{
frv_relocate_main_executable ();
if (!enable_break ())
{
warning (_("shared library handler failed to enable breakpoint"));
return;
}
}
static void
frv_clear_solib (void)
{
lm_base_cache = 0;
enable_break1_done = 0;
enable_break2_done = 0;
main_lm_addr = 0;
}
static void
frv_free_so (struct so_list *so)
{
xfree (so->lm_info->map);
xfree (so->lm_info->dyn_syms);
xfree (so->lm_info->dyn_relocs);
xfree (so->lm_info);
}
static void
frv_relocate_section_addresses (struct so_list *so,
struct section_table *sec)
{
int seg;
struct int_elf32_fdpic_loadmap *map;
map = so->lm_info->map;
for (seg = 0; seg < map->nsegs; seg++)
{
if (map->segs[seg].p_vaddr <= sec->addr
&& sec->addr < map->segs[seg].p_vaddr + map->segs[seg].p_memsz)
{
CORE_ADDR displ = map->segs[seg].addr - map->segs[seg].p_vaddr;
sec->addr += displ;
sec->endaddr += displ;
break;
}
}
}
static CORE_ADDR
main_got (void)
{
struct minimal_symbol *got_sym;
got_sym = lookup_minimal_symbol ("_GLOBAL_OFFSET_TABLE_", NULL, symfile_objfile);
if (got_sym == 0)
return 0;
return SYMBOL_VALUE_ADDRESS (got_sym);
}
CORE_ADDR
frv_fdpic_find_global_pointer (CORE_ADDR addr)
{
struct so_list *so;
so = master_so_list ();
while (so)
{
int seg;
struct int_elf32_fdpic_loadmap *map;
map = so->lm_info->map;
for (seg = 0; seg < map->nsegs; seg++)
{
if (map->segs[seg].addr <= addr
&& addr < map->segs[seg].addr + map->segs[seg].p_memsz)
return so->lm_info->got_value;
}
so = so->next;
}
return main_got ();
}
static CORE_ADDR find_canonical_descriptor_in_load_object
(CORE_ADDR, CORE_ADDR, char *, bfd *, struct lm_info *);
CORE_ADDR
frv_fdpic_find_canonical_descriptor (CORE_ADDR entry_point)
{
char *name;
CORE_ADDR addr;
CORE_ADDR got_value;
struct int_elf32_fdpic_loadmap *ldm = 0;
struct symbol *sym;
int status;
CORE_ADDR exec_loadmap_addr;
got_value = frv_fdpic_find_global_pointer (entry_point);
sym = find_pc_function (entry_point);
if (sym == 0)
name = 0;
else
name = SYMBOL_LINKAGE_NAME (sym);
addr = find_canonical_descriptor_in_load_object
(entry_point, got_value, name, symfile_objfile->obfd,
main_executable_lm_info);
if (addr == 0)
{
struct so_list *so;
so = master_so_list ();
while (so)
{
addr = find_canonical_descriptor_in_load_object
(entry_point, got_value, name, so->abfd, so->lm_info);
if (addr != 0)
break;
so = so->next;
}
}
return addr;
}
static CORE_ADDR
find_canonical_descriptor_in_load_object
(CORE_ADDR entry_point, CORE_ADDR got_value, char *name, bfd *abfd,
struct lm_info *lm)
{
arelent *rel;
unsigned int i;
CORE_ADDR addr = 0;
if (abfd == 0)
return 0;
if (lm == 0)
return 0;
if (lm->dyn_syms == NULL)
{
long storage_needed;
unsigned int number_of_symbols;
storage_needed = bfd_get_dynamic_symtab_upper_bound (abfd);
if (storage_needed <= 0)
return 0;
lm->dyn_syms = (asymbol **) xmalloc (storage_needed);
number_of_symbols = bfd_canonicalize_dynamic_symtab (abfd, lm->dyn_syms);
if (number_of_symbols == 0)
return 0;
}
if (lm->dyn_relocs == NULL)
{
long storage_needed;
storage_needed = bfd_get_dynamic_reloc_upper_bound (abfd);
if (storage_needed <= 0)
return 0;
lm->dyn_relocs = (arelent **) xmalloc (storage_needed);
lm->dyn_reloc_count
= bfd_canonicalize_dynamic_reloc (abfd, lm->dyn_relocs, lm->dyn_syms);
}
for (i = 0; i < lm->dyn_reloc_count; i++)
{
rel = lm->dyn_relocs[i];
if ((name == 0 || strcmp (name, (*rel->sym_ptr_ptr)->name) == 0)
&& rel->howto->type == R_FRV_FUNCDESC)
{
char buf[FRV_PTR_SIZE];
addr = rel->address + displacement_from_map (lm->map, rel->address);
if (target_read_memory (addr, buf, sizeof buf) != 0)
continue;
addr = extract_unsigned_integer (buf, sizeof buf);
if (target_read_memory (addr, buf, sizeof buf) != 0)
continue;
if (extract_unsigned_integer (buf, sizeof buf) != entry_point)
continue;
if (target_read_memory (addr + 4, buf, sizeof buf) != 0)
continue;
if (extract_unsigned_integer (buf, sizeof buf) != got_value)
continue;
break;
}
}
return addr;
}
CORE_ADDR
frv_fetch_objfile_link_map (struct objfile *objfile)
{
struct so_list *so;
if (main_lm_addr == 0)
solib_add (0, 0, 0, 1);
if (objfile == symfile_objfile)
return main_lm_addr;
for (so = master_so_list (); so; so = so->next)
{
if (so->objfile == objfile)
return so->lm_info->lm_addr;
}
return 0;
}
static struct target_so_ops frv_so_ops;
void
_initialize_frv_solib (void)
{
frv_so_ops.relocate_section_addresses = frv_relocate_section_addresses;
frv_so_ops.free_so = frv_free_so;
frv_so_ops.clear_solib = frv_clear_solib;
frv_so_ops.solib_create_inferior_hook = frv_solib_create_inferior_hook;
frv_so_ops.special_symbol_handling = frv_special_symbol_handling;
frv_so_ops.current_sos = frv_current_sos;
frv_so_ops.open_symbol_file_object = open_symbol_file_object;
frv_so_ops.in_dynsym_resolve_code = frv_in_dynsym_resolve_code;
current_target_so_ops = &frv_so_ops;
add_setshow_zinteger_cmd ("solib-frv", class_maintenance,
&solib_frv_debug, _("\
Set internal debugging of shared library code for FR-V."), _("\
Show internal debugging of shared library code for FR-V."), _("\
When non-zero, FR-V solib specific internal debugging is enabled."),
NULL,
NULL,
&setdebuglist, &showdebuglist);
}