From: Yao Qi Date: Sun, 14 Aug 2011 14:03:45 +0000 (+0000) Subject: gdb/ X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=8cd64e0034dc4dc3e48245f9f61fd1f5245fce66;p=binutils-gdb.git gdb/ * configure.tgt: Handle tic6x-*-*linux and tic6x-*-*. * solib-dsbt.c: New file. Support DSBT shared object. * tic6x-linux-tdep.c: New file. * tic6x-tdep.c: New file. * tic6x-tdep.h: New file. --- diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 534de9b1dbd..b0ed11e4453 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,13 @@ +2011-08-14 Andrew Jenner + Bernd Schmidt + Yao Qi + + * configure.tgt: Handle tic6x-*-*linux and tic6x-*-*. + * solib-dsbt.c: New file. Support DSBT shared object. + * tic6x-linux-tdep.c: New file. + * tic6x-tdep.c: New file. + * tic6x-tdep.h: New file. + 2011-08-14 Andrew Stubbs Yao Qi diff --git a/gdb/configure.tgt b/gdb/configure.tgt index eb05c2699d8..137debae6fb 100644 --- a/gdb/configure.tgt +++ b/gdb/configure.tgt @@ -552,6 +552,17 @@ spu*-*-*) build_gdbserver=yes ;; +tic6x-*-*linux) + # Target: GNU/Linux TI C6x + gdb_target_obs="tic6x-tdep.o tic6x-linux-tdep.o solib-dsbt.o \ + glibc-tdep.o corelow.o linux-tdep.o" + ;; + +tic6x-*-*) + # Target: TI C6X + gdb_target_obs="tic6x-tdep.o" + ;; + xstormy16-*-*) # Target: Sanyo Xstormy16a processor gdb_target_obs="xstormy16-tdep.o" diff --git a/gdb/solib-dsbt.c b/gdb/solib-dsbt.c new file mode 100644 index 00000000000..256939519f7 --- /dev/null +++ b/gdb/solib-dsbt.c @@ -0,0 +1,1259 @@ +/* Handle TIC6X (DSBT) shared libraries for GDB, the GNU Debugger. + Copyright (C) 2010, 2011 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include "defs.h" +#include "gdb_string.h" +#include "inferior.h" +#include "gdbcore.h" +#include "solib.h" +#include "solist.h" +#include "objfiles.h" +#include "symtab.h" +#include "language.h" +#include "command.h" +#include "gdbcmd.h" +#include "elf-bfd.h" +#include "exceptions.h" + +#define GOT_MODULE_OFFSET 4 + +/* Flag which indicates whether internal debug messages should be printed. */ +static int solib_dsbt_debug = 0; + +/* TIC6X pointers are four bytes wide. */ +enum { TIC6X_PTR_SIZE = 4 }; + +/* Representation of loadmap and related structs for the TIC6X DSBT. */ + +/* External versions; the size and alignment of the fields should be + the same as those on the target. When loaded, the placement of + the bits in each field will be the same as on the target. */ +typedef gdb_byte ext_Elf32_Half[2]; +typedef gdb_byte ext_Elf32_Addr[4]; +typedef gdb_byte ext_Elf32_Word[4]; + +struct ext_elf32_dsbt_loadseg +{ + /* Core address to which the segment is mapped. */ + ext_Elf32_Addr addr; + /* VMA recorded in the program header. */ + ext_Elf32_Addr p_vaddr; + /* Size of this segment in memory. */ + ext_Elf32_Word p_memsz; +}; + +struct ext_elf32_dsbt_loadmap { + /* Protocol version number, must be zero. */ + ext_Elf32_Word version; + /* A pointer to the DSBT table; the DSBT size and the index of this + module. */ + ext_Elf32_Word dsbt_table_ptr; + ext_Elf32_Word dsbt_size; + ext_Elf32_Word dsbt_index; + /* Number of segments in this map. */ + ext_Elf32_Word nsegs; + /* The actual memory map. */ + struct ext_elf32_dsbt_loadseg segs[1 /* nsegs, actually */]; +}; + +/* Internal versions; the types are GDB types and the data in each + of the fields is (or will be) decoded from the external struct + for ease of consumption. */ +struct int_elf32_dsbt_loadseg +{ + /* Core address to which the segment is mapped. */ + CORE_ADDR addr; + /* VMA recorded in the program header. */ + CORE_ADDR p_vaddr; + /* Size of this segment in memory. */ + long p_memsz; +}; + +struct int_elf32_dsbt_loadmap +{ + /* Protocol version number, must be zero. */ + int version; + CORE_ADDR dsbt_table_ptr; + /* A pointer to the DSBT table; the DSBT size and the index of this + module. */ + int dsbt_size, dsbt_index; + /* Number of segments in this map. */ + int nsegs; + /* The actual memory map. */ + struct int_elf32_dsbt_loadseg segs[1 /* nsegs, actually */]; +}; + +/* External link_map and elf32_dsbt_loadaddr struct definitions. */ + +typedef gdb_byte ext_ptr[4]; + +struct ext_elf32_dsbt_loadaddr +{ + ext_ptr map; /* struct elf32_dsbt_loadmap *map; */ +}; + +struct ext_link_map +{ + struct ext_elf32_dsbt_loadaddr l_addr; + + /* Absolute file name object was found in. */ + ext_ptr l_name; /* char *l_name; */ + + /* Dynamic section of the shared object. */ + ext_ptr l_ld; /* ElfW(Dyn) *l_ld; */ + + /* Chain of loaded objects. */ + ext_ptr l_next, l_prev; /* struct link_map *l_next, *l_prev; */ +}; + +/* Link map info to include in an allocated so_list entry */ + +struct lm_info +{ + /* The loadmap, digested into an easier to use form. */ + struct int_elf32_dsbt_loadmap *map; +}; + +/* Per pspace dsbt specific data. */ + +struct dsbt_info +{ + /* The load map, got value, etc. are not available from the chain + of loaded shared objects. ``main_executable_lm_info'' provides + a way to get at this information so that it doesn't need to be + frequently recomputed. Initialized by dsbt_relocate_main_executable. */ + struct lm_info *main_executable_lm_info; + + /* Load maps for the main executable and the interpreter. These are obtained + from ptrace. They are the starting point for getting into the program, + and are required to find the solib list with the individual load maps for + each module. */ + struct int_elf32_dsbt_loadmap *exec_loadmap; + struct int_elf32_dsbt_loadmap *interp_loadmap; + + /* Cached value for lm_base, below. */ + CORE_ADDR lm_base_cache; + + /* Link map address for main module. */ + CORE_ADDR main_lm_addr; + + int enable_break2_done; + + CORE_ADDR interp_text_sect_low; + CORE_ADDR interp_text_sect_high; + CORE_ADDR interp_plt_sect_low; + CORE_ADDR interp_plt_sect_high; +}; + +/* Per-program-space data key. */ +static const struct program_space_data *solib_dsbt_pspace_data; + +static void +dsbt_pspace_data_cleanup (struct program_space *pspace, void *arg) +{ + struct dsbt_info *info; + + info = program_space_data (pspace, solib_dsbt_pspace_data); + xfree (info); +} + +/* Get the current dsbt data. If none is found yet, add it now. This + function always returns a valid object. */ + +static struct dsbt_info * +get_dsbt_info (void) +{ + struct dsbt_info *info; + + info = program_space_data (current_program_space, solib_dsbt_pspace_data); + if (info != NULL) + return info; + + info = XZALLOC (struct dsbt_info); + set_program_space_data (current_program_space, solib_dsbt_pspace_data, info); + + info->enable_break2_done = 0; + info->lm_base_cache = 0; + info->main_lm_addr = 0; + + return info; +} + + +static void +dsbt_print_loadmap (struct int_elf32_dsbt_loadmap *map) +{ + int i; + + if (map == NULL) + printf_filtered ("(null)\n"); + else if (map->version != 0) + printf_filtered (_("Unsupported map version: %d\n"), map->version); + else + { + printf_filtered ("version %d\n", map->version); + + for (i = 0; i < map->nsegs; i++) + printf_filtered ("%s:%s -> %s:%s\n", + print_core_address (target_gdbarch, + map->segs[i].p_vaddr), + print_core_address (target_gdbarch, + map->segs[i].p_vaddr + + map->segs[i].p_memsz), + print_core_address (target_gdbarch, map->segs[i].addr), + print_core_address (target_gdbarch, map->segs[i].addr + + map->segs[i].p_memsz)); + } +} + +/* Decode int_elf32_dsbt_loadmap from BUF. */ + +static struct int_elf32_dsbt_loadmap * +decode_loadmap (gdb_byte *buf) +{ + enum bfd_endian byte_order = gdbarch_byte_order (target_gdbarch); + struct ext_elf32_dsbt_loadmap *ext_ldmbuf; + struct int_elf32_dsbt_loadmap *int_ldmbuf; + + int version, seg, nsegs; + int ext_ldmbuf_size, int_ldmbuf_size; + + ext_ldmbuf = (struct ext_elf32_dsbt_loadmap *) buf; + + /* Extract the version. */ + version = extract_unsigned_integer (ext_ldmbuf->version, + sizeof ext_ldmbuf->version, + byte_order); + if (version != 0) + { + /* We only handle version 0. */ + return NULL; + } + + /* Extract the number of segments. */ + nsegs = extract_unsigned_integer (ext_ldmbuf->nsegs, + sizeof ext_ldmbuf->nsegs, + byte_order); + + if (nsegs <= 0) + return NULL; + + /* Allocate space into which to put information extract from the + external loadsegs. I.e, allocate the internal loadsegs. */ + int_ldmbuf_size = (sizeof (struct int_elf32_dsbt_loadmap) + + (nsegs - 1) * sizeof (struct int_elf32_dsbt_loadseg)); + int_ldmbuf = xmalloc (int_ldmbuf_size); + + /* Place extracted information in internal structs. */ + 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), + byte_order); + int_ldmbuf->segs[seg].p_vaddr + = extract_unsigned_integer (ext_ldmbuf->segs[seg].p_vaddr, + sizeof (ext_ldmbuf->segs[seg].p_vaddr), + byte_order); + int_ldmbuf->segs[seg].p_memsz + = extract_unsigned_integer (ext_ldmbuf->segs[seg].p_memsz, + sizeof (ext_ldmbuf->segs[seg].p_memsz), + byte_order); + } + + xfree (ext_ldmbuf); + return int_ldmbuf; +} + + +static struct dsbt_info *get_dsbt_info (void); + +/* Interrogate the Linux kernel to find out where the program was loaded. + There are two load maps; one for the executable and one for the + interpreter (only in the case of a dynamically linked executable). */ + +static void +dsbt_get_initial_loadmaps (void) +{ + gdb_byte *buf; + struct dsbt_info *info = get_dsbt_info (); + + if (0 >= target_read_alloc (¤t_target, TARGET_OBJECT_FDPIC, + "exec", (gdb_byte**) &buf)) + { + info->exec_loadmap = NULL; + error (_("Error reading DSBT exec loadmap")); + } + info->exec_loadmap = decode_loadmap (buf); + if (solib_dsbt_debug) + dsbt_print_loadmap (info->exec_loadmap); + + if (0 >= target_read_alloc (¤t_target, TARGET_OBJECT_FDPIC, + "interp", (gdb_byte**)&buf)) + { + info->interp_loadmap = NULL; + error (_("Error reading DSBT interp loadmap")); + } + info->interp_loadmap = decode_loadmap (buf); + if (solib_dsbt_debug) + dsbt_print_loadmap (info->interp_loadmap); +} + +/* Given address LDMADDR, fetch and decode the loadmap at that address. + Return NULL if there is a problem reading the target memory or if + there doesn't appear to be a loadmap at the given address. The + allocated space (representing the loadmap) returned by this + function may be freed via a single call to xfree. */ + +static struct int_elf32_dsbt_loadmap * +fetch_loadmap (CORE_ADDR ldmaddr) +{ + enum bfd_endian byte_order = gdbarch_byte_order (target_gdbarch); + struct ext_elf32_dsbt_loadmap ext_ldmbuf_partial; + struct ext_elf32_dsbt_loadmap *ext_ldmbuf; + struct int_elf32_dsbt_loadmap *int_ldmbuf; + int ext_ldmbuf_size, int_ldmbuf_size; + int version, seg, nsegs; + + /* Fetch initial portion of the loadmap. */ + if (target_read_memory (ldmaddr, (gdb_byte *) &ext_ldmbuf_partial, + sizeof ext_ldmbuf_partial)) + { + /* Problem reading the target's memory. */ + return NULL; + } + + /* Extract the version. */ + version = extract_unsigned_integer (ext_ldmbuf_partial.version, + sizeof ext_ldmbuf_partial.version, + byte_order); + if (version != 0) + { + /* We only handle version 0. */ + return NULL; + } + + /* Extract the number of segments. */ + nsegs = extract_unsigned_integer (ext_ldmbuf_partial.nsegs, + sizeof ext_ldmbuf_partial.nsegs, + byte_order); + + if (nsegs <= 0) + return NULL; + + /* Allocate space for the complete (external) loadmap. */ + ext_ldmbuf_size = sizeof (struct ext_elf32_dsbt_loadmap) + + (nsegs - 1) * sizeof (struct ext_elf32_dsbt_loadseg); + ext_ldmbuf = xmalloc (ext_ldmbuf_size); + + /* Copy over the portion of the loadmap that's already been read. */ + memcpy (ext_ldmbuf, &ext_ldmbuf_partial, sizeof ext_ldmbuf_partial); + + /* Read the rest of the loadmap from the target. */ + if (target_read_memory (ldmaddr + sizeof ext_ldmbuf_partial, + (gdb_byte *) ext_ldmbuf + sizeof ext_ldmbuf_partial, + ext_ldmbuf_size - sizeof ext_ldmbuf_partial)) + { + /* Couldn't read rest of the loadmap. */ + xfree (ext_ldmbuf); + return NULL; + } + + /* Allocate space into which to put information extract from the + external loadsegs. I.e, allocate the internal loadsegs. */ + int_ldmbuf_size = sizeof (struct int_elf32_dsbt_loadmap) + + (nsegs - 1) * sizeof (struct int_elf32_dsbt_loadseg); + int_ldmbuf = xmalloc (int_ldmbuf_size); + + /* Place extracted information in internal structs. */ + 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), + byte_order); + int_ldmbuf->segs[seg].p_vaddr + = extract_unsigned_integer (ext_ldmbuf->segs[seg].p_vaddr, + sizeof (ext_ldmbuf->segs[seg].p_vaddr), + byte_order); + int_ldmbuf->segs[seg].p_memsz + = extract_unsigned_integer (ext_ldmbuf->segs[seg].p_memsz, + sizeof (ext_ldmbuf->segs[seg].p_memsz), + byte_order); + } + + xfree (ext_ldmbuf); + return int_ldmbuf; +} + +static void dsbt_relocate_main_executable (void); +static int enable_break2 (void); + +/* Scan for DYNTAG in .dynamic section of ABFD. If DYNTAG is found 1 is + returned and the corresponding PTR is set. */ + +static int +scan_dyntag (int dyntag, bfd *abfd, CORE_ADDR *ptr) +{ + int arch_size, step, sect_size; + long dyn_tag; + CORE_ADDR dyn_ptr, dyn_addr; + gdb_byte *bufend, *bufstart, *buf; + Elf32_External_Dyn *x_dynp_32; + Elf64_External_Dyn *x_dynp_64; + struct bfd_section *sect; + struct target_section *target_section; + + if (abfd == NULL) + return 0; + + if (bfd_get_flavour (abfd) != bfd_target_elf_flavour) + return 0; + + arch_size = bfd_get_arch_size (abfd); + if (arch_size == -1) + return 0; + + /* Find the start address of the .dynamic section. */ + sect = bfd_get_section_by_name (abfd, ".dynamic"); + if (sect == NULL) + return 0; + + for (target_section = current_target_sections->sections; + target_section < current_target_sections->sections_end; + target_section++) + if (sect == target_section->the_bfd_section) + break; + if (target_section < current_target_sections->sections_end) + dyn_addr = target_section->addr; + else + { + /* ABFD may come from OBJFILE acting only as a symbol file without being + loaded into the target (see add_symbol_file_command). This case is + such fallback to the file VMA address without the possibility of + having the section relocated to its actual in-memory address. */ + + dyn_addr = bfd_section_vma (abfd, sect); + } + + /* Read in .dynamic from the BFD. We will get the actual value + from memory later. */ + sect_size = bfd_section_size (abfd, sect); + buf = bufstart = alloca (sect_size); + if (!bfd_get_section_contents (abfd, sect, + buf, 0, sect_size)) + return 0; + + /* Iterate over BUF and scan for DYNTAG. If found, set PTR and return. */ + step = (arch_size == 32) ? sizeof (Elf32_External_Dyn) + : sizeof (Elf64_External_Dyn); + for (bufend = buf + sect_size; + buf < bufend; + buf += step) + { + if (arch_size == 32) + { + x_dynp_32 = (Elf32_External_Dyn *) buf; + dyn_tag = bfd_h_get_32 (abfd, (bfd_byte *) x_dynp_32->d_tag); + dyn_ptr = bfd_h_get_32 (abfd, (bfd_byte *) x_dynp_32->d_un.d_ptr); + } + else + { + x_dynp_64 = (Elf64_External_Dyn *) buf; + dyn_tag = bfd_h_get_64 (abfd, (bfd_byte *) x_dynp_64->d_tag); + dyn_ptr = bfd_h_get_64 (abfd, (bfd_byte *) x_dynp_64->d_un.d_ptr); + } + if (dyn_tag == DT_NULL) + return 0; + if (dyn_tag == dyntag) + { + /* If requested, try to read the runtime value of this .dynamic + entry. */ + if (ptr) + { + struct type *ptr_type; + gdb_byte ptr_buf[8]; + CORE_ADDR ptr_addr; + + ptr_type = builtin_type (target_gdbarch)->builtin_data_ptr; + ptr_addr = dyn_addr + (buf - bufstart) + arch_size / 8; + if (target_read_memory (ptr_addr, ptr_buf, arch_size / 8) == 0) + dyn_ptr = extract_typed_address (ptr_buf, ptr_type); + *ptr = dyn_ptr; + } + return 1; + } + } + + return 0; +} + +/* An expensive way to lookup the value of a single symbol for + bfd's that are only temporary anyway. This is used by the + shared library support to find the address of the debugger + interface structures in the shared library. + + Note that 0 is specifically allowed as an error return (no + such symbol). */ + +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) + { + /* Bfd symbols are section relative. */ + symaddr = sym->value + sym->section->vma; + break; + } + } + do_cleanups (back_to); + } + + if (symaddr) + return symaddr; + + /* Look for the symbol in the dynamic string table too. */ + + 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) + { + /* Bfd symbols are section relative. */ + symaddr = sym->value + sym->section->vma; + break; + } + } + do_cleanups (back_to); + } + + return symaddr; +} + + +/* If no open symbol file, attempt to locate and open the main symbol + file. + + If FROM_TTYP dereferences to a non-zero integer, allow messages to + be printed. This parameter is a pointer rather than an int because + open_symbol_file_object is called via catch_errors and + catch_errors requires a pointer argument. */ + +static int +open_symbol_file_object (void *from_ttyp) +{ + /* Unimplemented. */ + return 0; +} + +/* Given a loadmap and an address, return the displacement needed + to relocate the address. */ + +static CORE_ADDR +displacement_from_map (struct int_elf32_dsbt_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; +} + +/* Return the address from which the link map chain may be found. On + DSBT, a pointer to the start of the link map will be located at the + word found at base of GOT + GOT_MODULE_OFFSET. + + The base of GOT may be found in a number of ways. Assuming that the + main executable has already been relocated, + 1 The easiest way to find this value is to look up the address of + _GLOBAL_OFFSET_TABLE_. + 2 The other way is to look for tag DT_PLTGOT, which contains the virtual + address of Global Offset Table. .*/ + +static CORE_ADDR +lm_base (void) +{ + enum bfd_endian byte_order = gdbarch_byte_order (target_gdbarch); + struct minimal_symbol *got_sym; + CORE_ADDR addr; + gdb_byte buf[TIC6X_PTR_SIZE]; + struct dsbt_info *info = get_dsbt_info (); + + /* One of our assumptions is that the main executable has been relocated. + Bail out if this has not happened. (Note that post_create_inferior + in infcmd.c will call solib_add prior to solib_create_inferior_hook. + If we allow this to happen, lm_base_cache will be initialized with + a bogus value. */ + if (info->main_executable_lm_info == 0) + return 0; + + /* If we already have a cached value, return it. */ + if (info->lm_base_cache) + return info->lm_base_cache; + + got_sym = lookup_minimal_symbol ("_GLOBAL_OFFSET_TABLE_", NULL, + symfile_objfile); + + if (got_sym != 0) + { + addr = SYMBOL_VALUE_ADDRESS (got_sym); + if (solib_dsbt_debug) + fprintf_unfiltered (gdb_stdlog, + "lm_base: get addr %x by _GLOBAL_OFFSET_TABLE_.\n", + (unsigned int) addr); + } + else if (scan_dyntag (DT_PLTGOT, exec_bfd, &addr)) + { + struct int_elf32_dsbt_loadmap *ldm; + + dsbt_get_initial_loadmaps (); + ldm = info->exec_loadmap; + addr += displacement_from_map (ldm, addr); + if (solib_dsbt_debug) + fprintf_unfiltered (gdb_stdlog, + "lm_base: get addr %x by DT_PLTGOT.\n", + (unsigned int) addr); + } + else + { + if (solib_dsbt_debug) + fprintf_unfiltered (gdb_stdlog, + "lm_base: _GLOBAL_OFFSET_TABLE_ not found.\n"); + return 0; + } + addr += GOT_MODULE_OFFSET; + + if (solib_dsbt_debug) + fprintf_unfiltered (gdb_stdlog, + "lm_base: _GLOBAL_OFFSET_TABLE_ + %d = %s\n", + GOT_MODULE_OFFSET, hex_string_custom (addr, 8)); + + if (target_read_memory (addr, buf, sizeof buf) != 0) + return 0; + info->lm_base_cache = extract_unsigned_integer (buf, sizeof buf, byte_order); + + if (solib_dsbt_debug) + fprintf_unfiltered (gdb_stdlog, + "lm_base: lm_base_cache = %s\n", + hex_string_custom (info->lm_base_cache, 8)); + + return info->lm_base_cache; +} + + +/* Build a list of `struct so_list' objects describing the shared + objects currently loaded in the inferior. This list does not + include an entry for the main executable file. + + Note that we only gather information directly available from the + inferior --- we don't examine any of the shared library files + themselves. The declaration of `struct so_list' says which fields + we provide values for. */ + +static struct so_list * +dsbt_current_sos (void) +{ + enum bfd_endian byte_order = gdbarch_byte_order (target_gdbarch); + CORE_ADDR lm_addr; + struct so_list *sos_head = NULL; + struct so_list **sos_next_ptr = &sos_head; + struct dsbt_info *info = get_dsbt_info (); + + /* Make sure that the main executable has been relocated. This is + required in order to find the address of the global offset table, + which in turn is used to find the link map info. (See lm_base + for details.) + + Note that the relocation of the main executable is also performed + by SOLIB_CREATE_INFERIOR_HOOK, however, in the case of core + files, this hook is called too late in order to be of benefit to + SOLIB_ADD. SOLIB_ADD eventually calls this function, + dsbt_current_sos, and also precedes the call to + SOLIB_CREATE_INFERIOR_HOOK. (See post_create_inferior in + infcmd.c.) */ + if (info->main_executable_lm_info == 0 && core_bfd != NULL) + dsbt_relocate_main_executable (); + + /* Locate the address of the first link map struct. */ + lm_addr = lm_base (); + + /* We have at least one link map entry. Fetch the the lot of them, + building the solist chain. */ + while (lm_addr) + { + struct ext_link_map lm_buf; + ext_Elf32_Word indexword; + CORE_ADDR map_addr; + int dsbt_index; + int ret; + + if (solib_dsbt_debug) + fprintf_unfiltered (gdb_stdlog, + "current_sos: reading link_map entry at %s\n", + hex_string_custom (lm_addr, 8)); + + ret = target_read_memory (lm_addr, (gdb_byte *) &lm_buf, sizeof (lm_buf)); + if (ret) + { + warning (_("dsbt_current_sos: Unable to read link map entry." + " Shared object chain may be incomplete.")); + break; + } + + /* Fetch the load map address. */ + map_addr = extract_unsigned_integer (lm_buf.l_addr.map, + sizeof lm_buf.l_addr.map, + byte_order); + + ret = target_read_memory (map_addr + 12, (gdb_byte *) &indexword, + sizeof indexword); + if (ret) + { + warning (_("dsbt_current_sos: Unable to read dsbt index." + " Shared object chain may be incomplete.")); + break; + } + dsbt_index = extract_unsigned_integer (indexword, sizeof indexword, + byte_order); + + /* If the DSBT index is zero, then we're looking at the entry + for the main executable. By convention, we don't include + this in the list of shared objects. */ + if (dsbt_index != 0) + { + int errcode; + char *name_buf; + struct int_elf32_dsbt_loadmap *loadmap; + struct so_list *sop; + CORE_ADDR addr; + + loadmap = fetch_loadmap (map_addr); + if (loadmap == NULL) + { + warning (_("dsbt_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; + /* Fetch the name. */ + addr = extract_unsigned_integer (lm_buf.l_name, + sizeof (lm_buf.l_name), + byte_order); + target_read_string (addr, &name_buf, SO_NAME_MAX_PATH_SIZE - 1, + &errcode); + + if (errcode != 0) + warning (_("Can't read pathname for link map entry: %s."), + safe_strerror (errcode)); + else + { + if (solib_dsbt_debug) + fprintf_unfiltered (gdb_stdlog, "current_sos: name = %s\n", + name_buf); + + 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 + { + info->main_lm_addr = lm_addr; + } + + lm_addr = extract_unsigned_integer (lm_buf.l_next, + sizeof (lm_buf.l_next), byte_order); + } + + enable_break2 (); + + return sos_head; +} + +/* Return 1 if PC lies in the dynamic symbol resolution code of the + run time loader. */ + +static int +dsbt_in_dynsym_resolve_code (CORE_ADDR pc) +{ + struct dsbt_info *info = get_dsbt_info (); + + return ((pc >= info->interp_text_sect_low && pc < info->interp_text_sect_high) + || (pc >= info->interp_plt_sect_low && pc < info->interp_plt_sect_high) + || in_plt_section (pc, NULL)); +} + +/* Print a warning about being unable to set the dynamic linker + breakpoint. */ + +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.")); +} + +/* The dynamic linkers has, as part of its debugger interface, support + for arranging for the inferior to hit a breakpoint after mapping in + the shared libraries. This function enables that breakpoint. + + On the TIC6X, using the shared library (DSBT), the symbol + _dl_debug_addr points to the r_debug struct which contains + a field called r_brk. r_brk is the address of the function + descriptor upon which a breakpoint must be placed. Being a + function descriptor, we must extract the entry point in order + to set the breakpoint. + + Our strategy will be to get the .interp section from the + executable. This section will provide us with the name of the + interpreter. We'll open the interpreter and then look up + the address of _dl_debug_addr. We then relocate this address + using the interpreter's loadmap. Once the relocated address + is known, we fetch the value (address) corresponding to r_brk + and then use that value to fetch the entry point of the function + we're interested in. */ + +static int +enable_break2 (void) +{ + enum bfd_endian byte_order = gdbarch_byte_order (target_gdbarch); + int success = 0; + char **bkpt_namep; + asection *interp_sect; + struct dsbt_info *info = get_dsbt_info (); + + if (exec_bfd == NULL) + return 0; + + if (!target_has_execution) + return 0; + + if (info->enable_break2_done) + return 1; + + info->interp_text_sect_low = 0; + info->interp_text_sect_high = 0; + info->interp_plt_sect_low = 0; + info->interp_plt_sect_high = 0; + + /* Find the .interp section; if not found, warn the user and drop + into the old breakpoint at symbol code. */ + interp_sect = bfd_get_section_by_name (exec_bfd, ".interp"); + if (interp_sect) + { + unsigned int interp_sect_size; + gdb_byte *buf; + bfd *tmp_bfd = NULL; + int status; + CORE_ADDR addr, interp_loadmap_addr; + gdb_byte addr_buf[TIC6X_PTR_SIZE]; + struct int_elf32_dsbt_loadmap *ldm; + volatile struct gdb_exception ex; + + /* Read the contents of the .interp section into a local buffer; + the contents specify the dynamic linker this program uses. */ + 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); + + /* Now we need to figure out where the dynamic linker was + loaded so that we can load its symbols and place a breakpoint + in the dynamic linker itself. */ + + TRY_CATCH (ex, RETURN_MASK_ALL) + { + tmp_bfd = solib_bfd_open (buf); + } + if (tmp_bfd == NULL) + { + enable_break_failure_warning (); + return 0; + } + + dsbt_get_initial_loadmaps (); + ldm = info->interp_loadmap; + + /* Record the relocated start and end address of the dynamic linker + text and plt section for dsbt_in_dynsym_resolve_code. */ + interp_sect = bfd_get_section_by_name (tmp_bfd, ".text"); + if (interp_sect) + { + info->interp_text_sect_low + = bfd_section_vma (tmp_bfd, interp_sect); + info->interp_text_sect_low + += displacement_from_map (ldm, info->interp_text_sect_low); + info->interp_text_sect_high + = info->interp_text_sect_low + + bfd_section_size (tmp_bfd, interp_sect); + } + interp_sect = bfd_get_section_by_name (tmp_bfd, ".plt"); + if (interp_sect) + { + info->interp_plt_sect_low = + bfd_section_vma (tmp_bfd, interp_sect); + info->interp_plt_sect_low + += displacement_from_map (ldm, info->interp_plt_sect_low); + info->interp_plt_sect_high = + info->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_dsbt_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_dsbt_debug) + fprintf_unfiltered (gdb_stdlog, + "enable_break: _dl_debug_addr (after relocation) = %s\n", + hex_string_custom (addr, 8)); + + /* Fetch the address of the r_debug struct. */ + 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, byte_order); + + if (solib_dsbt_debug) + fprintf_unfiltered (gdb_stdlog, + "enable_break: _dl_debug_addr[0..3] = %s\n", + hex_string_custom (addr, 8)); + + /* If it's zero, then the ldso hasn't initialized yet, and so + there are no shared libs yet loaded. */ + if (addr == 0) + { + if (solib_dsbt_debug) + fprintf_unfiltered (gdb_stdlog, + "enable_break: ldso not yet initialized\n"); + /* Do not warn, but mark to run again. */ + return 0; + } + + /* Fetch the r_brk field. It's 8 bytes from the start of + _dl_debug_addr. */ + 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, byte_order); + + /* We're done with the temporary bfd. */ + bfd_close (tmp_bfd); + + /* We're also done with the loadmap. */ + xfree (ldm); + + /* Remove all the solib event breakpoints. Their addresses + may have changed since the last time we ran the program. */ + remove_solib_event_breakpoints (); + + /* Now (finally!) create the solib breakpoint. */ + create_solib_event_breakpoint (target_gdbarch, addr); + + info->enable_break2_done = 1; + + return 1; + } + + /* Tell the user we couldn't set a dynamic linker breakpoint. */ + enable_break_failure_warning (); + + /* Failure return. */ + return 0; +} + +static int +enable_break (void) +{ + asection *interp_sect; + struct minimal_symbol *start; + + /* Check for the presence of a .interp section. If there is no + such section, the executable is statically linked. */ + + interp_sect = bfd_get_section_by_name (exec_bfd, ".interp"); + + if (interp_sect == NULL) + { + if (solib_dsbt_debug) + fprintf_unfiltered (gdb_stdlog, + "enable_break: No .interp section found.\n"); + return 0; + } + + start = lookup_minimal_symbol ("_start", NULL, symfile_objfile); + if (start == NULL) + { + if (solib_dsbt_debug) + fprintf_unfiltered (gdb_stdlog, + "enable_break: symbol _start is not found.\n"); + return 0; + } + + create_solib_event_breakpoint (target_gdbarch, + SYMBOL_VALUE_ADDRESS (start)); + + if (solib_dsbt_debug) + fprintf_unfiltered (gdb_stdlog, + "enable_break: solib event breakpoint placed at : %s\n", + hex_string_custom (SYMBOL_VALUE_ADDRESS (start), 8)); + return 1; +} + +/* Once the symbols from a shared object have been loaded in the usual + way, we are called to do any system specific symbol handling that + is needed. */ + +static void +dsbt_special_symbol_handling (void) +{ +} + +static void +dsbt_relocate_main_executable (void) +{ + int status; + CORE_ADDR exec_addr, interp_addr; + struct int_elf32_dsbt_loadmap *ldm; + struct cleanup *old_chain; + struct section_offsets *new_offsets; + int changed; + struct obj_section *osect; + struct dsbt_info *info = get_dsbt_info (); + + dsbt_get_initial_loadmaps (); + ldm = info->exec_loadmap; + + xfree (info->main_executable_lm_info); + info->main_executable_lm_info = xcalloc (1, sizeof (struct lm_info)); + 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; + + /* Current address of section. */ + addr = obj_section_addr (osect); + /* Offset from where this section started. */ + offset = ANOFFSET (symfile_objfile->section_offsets, osect_idx); + /* Original address prior to any past relocations. */ + 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); + + /* Now that symfile_objfile has been relocated, we can compute the + GOT value and stash it away. */ +} + +/* When gdb starts up the inferior, it nurses it along (through the + shell) until it is ready to execute it's first instruction. At this + point, this function gets called via expansion of the macro + SOLIB_CREATE_INFERIOR_HOOK. + + For the DSBT shared library, the main executable needs to be relocated. + The shared library breakpoints also need to be enabled. + */ + +static void +dsbt_solib_create_inferior_hook (int from_tty) +{ + /* Relocate main executable. */ + dsbt_relocate_main_executable (); + + /* Enable shared library breakpoints. */ + if (!enable_break ()) + { + warning (_("shared library handler failed to enable breakpoint")); + return; + } +} + +static void +dsbt_clear_solib (void) +{ + struct dsbt_info *info = get_dsbt_info (); + + info->lm_base_cache = 0; + info->enable_break2_done = 0; + info->main_lm_addr = 0; + if (info->main_executable_lm_info != 0) + { + xfree (info->main_executable_lm_info->map); + xfree (info->main_executable_lm_info); + info->main_executable_lm_info = 0; + } +} + +static void +dsbt_free_so (struct so_list *so) +{ + xfree (so->lm_info->map); + xfree (so->lm_info); +} + +static void +dsbt_relocate_section_addresses (struct so_list *so, + struct target_section *sec) +{ + int seg; + struct int_elf32_dsbt_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 void +show_dsbt_debug (struct ui_file *file, int from_tty, + struct cmd_list_element *c, const char *value) +{ + fprintf_filtered (file, _("solib-dsbt debugging is %s.\n"), value); +} + +struct target_so_ops dsbt_so_ops; + +/* Provide a prototype to silence -Wmissing-prototypes. */ +extern initialize_file_ftype _initialize_dsbt_solib; + +void +_initialize_dsbt_solib (void) +{ + solib_dsbt_pspace_data + = register_program_space_data_with_cleanup (dsbt_pspace_data_cleanup); + + dsbt_so_ops.relocate_section_addresses = dsbt_relocate_section_addresses; + dsbt_so_ops.free_so = dsbt_free_so; + dsbt_so_ops.clear_solib = dsbt_clear_solib; + dsbt_so_ops.solib_create_inferior_hook = dsbt_solib_create_inferior_hook; + dsbt_so_ops.special_symbol_handling = dsbt_special_symbol_handling; + dsbt_so_ops.current_sos = dsbt_current_sos; + dsbt_so_ops.open_symbol_file_object = open_symbol_file_object; + dsbt_so_ops.in_dynsym_resolve_code = dsbt_in_dynsym_resolve_code; + dsbt_so_ops.bfd_open = solib_bfd_open; + + /* Debug this file's internals. */ + add_setshow_zinteger_cmd ("solib-dsbt", class_maintenance, + &solib_dsbt_debug, _("\ +Set internal debugging of shared library code for DSBT ELF."), _("\ +Show internal debugging of shared library code for DSBT ELF."), _("\ +When non-zero, DSBT solib specific internal debugging is enabled."), + NULL, + show_dsbt_debug, + &setdebuglist, &showdebuglist); +} diff --git a/gdb/tic6x-linux-tdep.c b/gdb/tic6x-linux-tdep.c new file mode 100644 index 00000000000..0e4d3272359 --- /dev/null +++ b/gdb/tic6x-linux-tdep.c @@ -0,0 +1,227 @@ +/* GNU/Linux on TI C6x target support. + Copyright (C) 2011 + Free Software Foundation, Inc. + Contributed by Yao Qi + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include "defs.h" +#include "solib.h" +#include "osabi.h" +#include "linux-tdep.h" +#include "tic6x-tdep.h" +#include "trad-frame.h" +#include "tramp-frame.h" +#include "gdb_assert.h" +#include "elf-bfd.h" +#include "elf/tic6x.h" + +#include "features/tic6x-c64xp-linux.c" +#include "features/tic6x-c64x-linux.c" +#include "features/tic6x-c62x-linux.c" + +/* The offset from rt_sigframe pointer to SP register. */ +#define TIC6X_SP_RT_SIGFRAME 8 +/* Size of struct siginfo info. */ +#define TIC6X_SIGINFO_SIZE 128 +/* Size of type stack_t, which contains three fields of type void*, int, and + size_t respectively. */ +#define TIC6X_STACK_T_SIZE (3 * 4) + +const gdb_byte tic6x_bkpt_illegal_opcode_be[] = { 0x56, 0x45, 0x43, 0x14 }; +const gdb_byte tic6x_bkpt_illegal_opcode_le[] = { 0x14, 0x43, 0x45, 0x56 }; + +static const gdb_byte tic6x_bkpt_bnop_be[] = { 0x00, 0x00, 0xa1, 0x22 }; +static const gdb_byte tic6x_bkpt_bnop_le[] = { 0x22, 0xa1, 0x00, 0x00 }; + +/* Return the offset of register REGNUM in struct sigcontext. Return 0 if no + such register in sigcontext. */ + +static unsigned int +tic6x_register_sigcontext_offset (unsigned int regnum, struct gdbarch *gdbarch) +{ + struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch); + + if (regnum == TIC6X_A4_REGNUM || regnum == TIC6X_A4_REGNUM + 2 + || regnum == TIC6X_A4_REGNUM + 4) + return 4 * (regnum - TIC6X_A4_REGNUM + 2); /* A4, A6, A8 */ + else if (regnum == TIC6X_A5_REGNUM || regnum == TIC6X_A5_REGNUM + 2 + || regnum == TIC6X_A5_REGNUM + 4) + return 4 * (regnum - TIC6X_A5_REGNUM + 12); /* A5, A7, A9 */ + else if (regnum == TIC6X_B4_REGNUM || regnum == TIC6X_B4_REGNUM + 2 + || regnum == TIC6X_B4_REGNUM + 4) + return 4 * (regnum - TIC6X_B4_REGNUM + 3); /* B4, B6, B8 */ + else if (regnum == TIC6X_B5_REGNUM || regnum == TIC6X_B5_REGNUM + 2 + || regnum == TIC6X_B5_REGNUM + 4) + return 4 * (regnum - TIC6X_B5_REGNUM + 19); /* B5, B7, B9 */ + else if (regnum >= 0 && regnum < TIC6X_A4_REGNUM) + return 4 * (regnum - 0 + 8); /* A0 - A3 */ + else if (regnum >= TIC6X_B0_REGNUM && regnum < TIC6X_B4_REGNUM) + return 4 * (regnum - TIC6X_B0_REGNUM + 15); /* B0 - B3 */ + else if (regnum >= 34 && regnum < 34 + 32) + return 4 * (regnum - 34 + 23); /* A16 - A31, B16 - B31 */ + else if (regnum == TIC6X_PC_REGNUM) + return 4 * (tdep->has_gp ? 55 : 23); + else if (regnum == TIC6X_SP_REGNUM) + return 4; + + return 0; +} + +/* Support unwinding frame in signal trampoline. We don't check sigreturn, + since it is not used in kernel. */ + +static void +tic6x_linux_rt_sigreturn_init (const struct tramp_frame *self, + struct frame_info *this_frame, + struct trad_frame_cache *this_cache, + CORE_ADDR func) +{ + struct gdbarch *gdbarch = get_frame_arch (this_frame); + CORE_ADDR sp = get_frame_register_unsigned (this_frame, TIC6X_SP_REGNUM); + /* The base of struct sigcontext is computed by examining the definition of + struct rt_sigframe in linux kernel source arch/c6x/kernel/signal.c. */ + CORE_ADDR base = (sp + TIC6X_SP_RT_SIGFRAME + /* Pointer type *pinfo and *puc in struct rt_sigframe. */ + + 4 + 4 + + TIC6X_SIGINFO_SIZE + + 4 + 4 /* uc_flags and *uc_link in struct ucontext. */ + + TIC6X_STACK_T_SIZE); + struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch); + unsigned int reg_offset; + unsigned int i; + + for (i = 0; i < 10; i++) /* A0 - A9 */ + { + reg_offset = tic6x_register_sigcontext_offset (i, gdbarch); + gdb_assert (reg_offset != 0); + + trad_frame_set_reg_addr (this_cache, i, base + reg_offset); + } + + for (i = TIC6X_B0_REGNUM; i < TIC6X_B0_REGNUM + 10; i++) /* B0 - B9 */ + { + reg_offset = tic6x_register_sigcontext_offset (i, gdbarch); + gdb_assert (reg_offset != 0); + + trad_frame_set_reg_addr (this_cache, i, base + reg_offset); + } + + if (tdep->has_gp) + for (i = 34; i < 34 + 32; i++) /* A16 - A31, B16 - B31 */ + { + reg_offset = tic6x_register_sigcontext_offset (i, gdbarch); + gdb_assert (reg_offset != 0); + + trad_frame_set_reg_addr (this_cache, i, base + reg_offset); + } + + trad_frame_set_reg_addr (this_cache, TIC6X_PC_REGNUM, + base + tic6x_register_sigcontext_offset (TIC6X_PC_REGNUM, + gdbarch)); + trad_frame_set_reg_addr (this_cache, TIC6X_SP_REGNUM, + base + tic6x_register_sigcontext_offset (TIC6X_SP_REGNUM, + gdbarch)); + + /* Save a frame ID. */ + trad_frame_set_id (this_cache, frame_id_build (sp, func)); +} + +static struct tramp_frame tic6x_linux_rt_sigreturn_tramp_frame = +{ + SIGTRAMP_FRAME, + 4, + { + {0x000045aa, 0x0fffffff}, /* mvk .S2 139,b0 */ + {0x10000000, -1}, /* swe */ + {TRAMP_SENTINEL_INSN} + }, + tic6x_linux_rt_sigreturn_init +}; + +/* When FRAME is at a syscall instruction, return the PC of the next + instruction to be executed. */ + +static CORE_ADDR +tic6x_linux_syscall_next_pc (struct frame_info *frame) +{ + ULONGEST syscall_number = get_frame_register_unsigned (frame, + TIC6X_B0_REGNUM); + CORE_ADDR pc = get_frame_pc (frame); + + if (syscall_number == 139 /* rt_sigreturn */) + return frame_unwind_caller_pc (frame); + + return pc + 4; +} + + +extern struct target_so_ops dsbt_so_ops; +static void +tic6x_uclinux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch) +{ + struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch); + + linux_init_abi (info, gdbarch); + + /* Shared library handling. */ + set_solib_ops (gdbarch, &dsbt_so_ops); + + tdep->syscall_next_pc = tic6x_linux_syscall_next_pc; + +#ifdef HAVE_ELF + /* In tic6x Linux kernel, breakpoint instructions varies on different archs. + On C64x+ and C67x+, breakpoint instruction is 0x56454314, which is an + illegal opcode. On other arch, breakpoint instruction is 0x0000a122 + (BNOP .S2 0,5). */ + if (info.abfd) + switch (bfd_elf_get_obj_attr_int (info.abfd, OBJ_ATTR_PROC, Tag_ISA)) + { + case C6XABI_Tag_ISA_C64XP: + case C6XABI_Tag_ISA_C67XP: + if (info.byte_order == BFD_ENDIAN_BIG) + tdep->breakpoint = tic6x_bkpt_illegal_opcode_be; + else + tdep->breakpoint = tic6x_bkpt_illegal_opcode_le; + break; + default: + { + if (info.byte_order == BFD_ENDIAN_BIG) + tdep->breakpoint = tic6x_bkpt_bnop_be; + else + tdep->breakpoint = tic6x_bkpt_bnop_le; + } + } +#endif + + /* Signal trampoline support. */ + tramp_frame_prepend_unwinder (gdbarch, + &tic6x_linux_rt_sigreturn_tramp_frame); +} + +/* Provide a prototype to silence -Wmissing-prototypes. */ +extern initialize_file_ftype _initialize_tic6x_linux_tdep; + +void +_initialize_tic6x_linux_tdep (void) +{ + gdbarch_register_osabi (bfd_arch_tic6x, 0, GDB_OSABI_LINUX, + tic6x_uclinux_init_abi); + + initialize_tdesc_tic6x_c64xp_linux (); + initialize_tdesc_tic6x_c64x_linux (); + initialize_tdesc_tic6x_c62x_linux (); +} diff --git a/gdb/tic6x-tdep.c b/gdb/tic6x-tdep.c new file mode 100644 index 00000000000..eedb5e9129e --- /dev/null +++ b/gdb/tic6x-tdep.c @@ -0,0 +1,1386 @@ +/* Target dependent code for GDB on TI C6x systems. + + Copyright (C) 2010, 2011. + Free Software Foundation, Inc. + Contributed by Andrew Jenner + Contributed by Yao Qi + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include "defs.h" +#include "frame.h" +#include "frame-unwind.h" +#include "frame-base.h" +#include "trad-frame.h" +#include "dwarf2-frame.h" +#include "symtab.h" +#include "inferior.h" +#include "gdbtypes.h" +#include "gdbcore.h" +#include "gdbcmd.h" +#include "target.h" +#include "dis-asm.h" +#include "regcache.h" +#include "value.h" +#include "symfile.h" +#include "arch-utils.h" +#include "floatformat.h" +#include "glibc-tdep.h" +#include "infcall.h" +#include "regset.h" +#include "tramp-frame.h" +#include "linux-tdep.h" +#include "solib.h" +#include "objfiles.h" +#include "gdb_assert.h" +#include "osabi.h" +#include "tic6x-tdep.h" +#include "language.h" +#include "target-descriptions.h" + +#include "features/tic6x-c64xp.c" +#include "features/tic6x-c64x.c" +#include "features/tic6x-c62x.c" + +#define TIC6X_OPCODE_SIZE 4 +#define TIC6X_FETCH_PACKET_SIZE 32 + +#define INST_S_BIT(INST) ((INST >> 1) & 1) +#define INST_X_BIT(INST) ((INST >> 12) & 1) + +struct tic6x_unwind_cache +{ + /* The frame's base, optionally used by the high-level debug info. */ + CORE_ADDR base; + + /* The previous frame's inner most stack address. Used as this + frame ID's stack_addr. */ + CORE_ADDR cfa; + + /* The address of the first instruction in this function */ + CORE_ADDR pc; + + /* Which register holds the return address for the frame. */ + int return_regnum; + + /* The offset of register saved on stack. If register is not saved, the + corresponding element is -1. */ + CORE_ADDR reg_saved[TIC6X_NUM_CORE_REGS]; +}; + + +/* Name of TI C6x core registers. */ +static const char *const tic6x_register_names[] = +{ + "A0", "A1", "A2", "A3", /* 0 1 2 3 */ + "A4", "A5", "A6", "A7", /* 4 5 6 7 */ + "A8", "A9", "A10", "A11", /* 8 9 10 11 */ + "A12", "A13", "A14", "A15", /* 12 13 14 15 */ + "B0", "B1", "B2", "B3", /* 16 17 18 19 */ + "B4", "B5", "B6", "B7", /* 20 21 22 23 */ + "B8", "B9", "B10", "B11", /* 24 25 26 27 */ + "B12", "B13", "B14", "B15", /* 28 29 30 31 */ + "CSR", "PC", /* 32 33 */ +}; + +/* This array maps the arguments to the register number which passes argument + in function call according to C6000 ELF ABI. */ +static const int arg_regs[] = { 4, 20, 6, 22, 8, 24, 10, 26, 12, 28 }; + +/* This is the implementation of gdbarch method register_name. */ + +static const char * +tic6x_register_name (struct gdbarch *gdbarch, int regno) +{ + if (regno < 0) + return NULL; + + if (tdesc_has_registers (gdbarch_target_desc (gdbarch))) + return tdesc_register_name (gdbarch, regno); + else if (regno >= ARRAY_SIZE (tic6x_register_names)) + return ""; + else + return tic6x_register_names[regno]; +} + +/* This is the implementation of gdbarch method register_type. */ + +static struct type * +tic6x_register_type (struct gdbarch *gdbarch, int regno) +{ + + if (regno == TIC6X_PC_REGNUM) + return builtin_type (gdbarch)->builtin_func_ptr; + else + return builtin_type (gdbarch)->builtin_uint32; +} + +static void +tic6x_setup_default (struct tic6x_unwind_cache *cache) +{ + int i; + + for (i = 0; i < TIC6X_NUM_CORE_REGS; i++) + cache->reg_saved[i] = -1; +} + +static unsigned long tic6x_fetch_instruction (struct gdbarch *, CORE_ADDR); +static int tic6x_register_number (int reg, int side, int crosspath); + +/* Do a full analysis of the prologue at START_PC and update CACHE accordingly. + Bail out early if CURRENT_PC is reached. Returns the address of the first + instruction after the prologue. */ + +CORE_ADDR +tic6x_analyze_prologue (struct gdbarch *gdbarch, const CORE_ADDR start_pc, + const CORE_ADDR current_pc, + struct tic6x_unwind_cache *cache, + struct frame_info *this_frame) +{ + enum bfd_endian byte_order = gdbarch_byte_order (gdbarch); + unsigned long inst; + unsigned int src_reg, base_reg, dst_reg; + int i; + CORE_ADDR pc = start_pc; + CORE_ADDR return_pc = start_pc; + int frame_base_offset_to_sp = 0; + /* Counter of non-stw instructions after first insn ` sub sp, xxx, sp'. */ + int non_stw_insn_counter = 0; + + if (start_pc >= current_pc) + return_pc = current_pc; + + cache->base = 0; + + /* The landmarks in prologue is one or two SUB instructions to SP. + Instructions on setting up dsbt are in the last part of prologue, if + needed. In maxim, prologue can be divided to three parts by two + `sub sp, xx, sp' insns. */ + + /* Step 1: Look for the 1st and 2nd insn `sub sp, xx, sp', in which, the + 2nd one is optional. */ + while (pc < current_pc) + { + int offset = 0; + + unsigned long inst = tic6x_fetch_instruction (gdbarch, pc); + + if ((inst & 0x1ffc) == 0x1dc0 || (inst & 0x1ffc) == 0x1bc0 + || (inst & 0x0ffc) == 0x9c0) + { + /* SUBAW/SUBAH/SUB, and src1 is ucst 5. */ + unsigned int src2 = tic6x_register_number ((inst >> 18) & 0x1f, + INST_S_BIT (inst), 0); + unsigned int dst = tic6x_register_number ((inst >> 23) & 0x1f, + INST_S_BIT (inst), 0); + + if (src2 == TIC6X_SP_REGNUM && dst == TIC6X_SP_REGNUM) + { + /* Extract const from insn SUBAW/SUBAH/SUB, and translate it to + offset. The constant offset is decoded in bit 13-17 in all + these three kinds of instructions. */ + unsigned int ucst5 = (inst >> 13) & 0x1f; + + if ((inst & 0x1ffc) == 0x1dc0) /* SUBAW */ + frame_base_offset_to_sp += ucst5 << 2; + else if ((inst & 0x1ffc) == 0x1bc0) /* SUBAH */ + frame_base_offset_to_sp += ucst5 << 1; + else if ((inst & 0x0ffc) == 0x9c0) /* SUB */ + frame_base_offset_to_sp += ucst5; + else + gdb_assert_not_reached ("unexpected instruction"); + + return_pc = pc + 4; + } + } + else if ((inst & 0x174) == 0x74) /* stw SRC, *+b15(uconst) */ + { + /* The y bit determines which file base is read from. */ + base_reg = tic6x_register_number ((inst >> 18) & 0x1f, + (inst >> 7) & 1, 0); + + if (base_reg == TIC6X_SP_REGNUM) + { + src_reg = tic6x_register_number ((inst >> 23) & 0x1f, + INST_S_BIT (inst), 0); + + cache->reg_saved[src_reg] = ((inst >> 13) & 0x1f) << 2; + + return_pc = pc + 4; + } + non_stw_insn_counter = 0; + } + else + { + non_stw_insn_counter++; + /* Following instruction sequence may be emitted in prologue: + + <+0>: subah .D2 b15,28,b15 + <+4>: or .L2X 0,a4,b0 + <+8>: || stw .D2T2 b14,*+b15(56) + <+12>:[!b0] b .S1 0xe50e4c1c + <+16>:|| stw .D2T1 a10,*+b15(48) + <+20>:stw .D2T2 b3,*+b15(52) + <+24>:stw .D2T1 a4,*+b15(40) + + we should look forward for next instruction instead of breaking loop + here. So far, we allow almost two sequential non-stw instructions + in prologue. */ + if (non_stw_insn_counter >= 2) + break; + } + + + pc += 4; + } + /* Step 2: Skip insn on setting up dsbt if it is. Usually, it looks like, + ldw .D2T2 *+b14(0),b14 */ + inst = tic6x_fetch_instruction (gdbarch, pc); + /* The s bit determines which file dst will be loaded into, same effect as + other places. */ + dst_reg = tic6x_register_number ((inst >> 23) & 0x1f, (inst >> 1) & 1, 0); + /* The y bit (bit 7), instead of s bit, determines which file base be + used. */ + base_reg = tic6x_register_number ((inst >> 18) & 0x1f, (inst >> 7) & 1, 0); + + if ((inst & 0x164) == 0x64 /* ldw */ + && dst_reg == TIC6X_DP_REGNUM /* dst is B14 */ + && base_reg == TIC6X_DP_REGNUM) /* baseR is B14 */ + { + return_pc = pc + 4; + } + + if (this_frame) + { + cache->base = get_frame_register_unsigned (this_frame, TIC6X_SP_REGNUM); + + if (cache->reg_saved[TIC6X_FP_REGNUM] != -1) + { + /* If the FP now holds an offset from the CFA then this is a frame + which uses the frame pointer. */ + + cache->cfa = get_frame_register_unsigned (this_frame, + TIC6X_FP_REGNUM); + } + else + { + /* FP doesn't hold an offset from the CFA. If SP still holds an + offset from the CFA then we might be in a function which omits + the frame pointer. */ + + cache->cfa = cache->base + frame_base_offset_to_sp; + } + } + + /* Adjust all the saved registers such that they contain addresses + instead of offsets. */ + for (i = 0; i < TIC6X_NUM_CORE_REGS; i++) + if (cache->reg_saved[i] != -1) + cache->reg_saved[i] = cache->base + cache->reg_saved[i]; + + return return_pc; +} + +/* This is the implementation of gdbarch method skip_prologue. */ + +CORE_ADDR +tic6x_skip_prologue (struct gdbarch *gdbarch, CORE_ADDR start_pc) +{ + CORE_ADDR limit_pc; + CORE_ADDR func_addr; + struct tic6x_unwind_cache cache; + + /* See if we can determine the end of the prologue via the symbol table. + If so, then return either PC, or the PC after the prologue, whichever is + greater. */ + if (find_pc_partial_function (start_pc, NULL, &func_addr, NULL)) + { + CORE_ADDR post_prologue_pc + = skip_prologue_using_sal (gdbarch, func_addr); + if (post_prologue_pc != 0) + return max (start_pc, post_prologue_pc); + } + + /* Can't determine prologue from the symbol table, need to examine + instructions. */ + return tic6x_analyze_prologue (gdbarch, start_pc, (CORE_ADDR) -1, &cache, + NULL); +} + +/* This is the implementation of gdbarch method breakpiont_from_pc. */ + +const unsigned char* +tic6x_breakpoint_from_pc (struct gdbarch *gdbarch, CORE_ADDR *bp_addr, + int *bp_size) +{ + struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch); + + *bp_size = 4; + + if (tdep == NULL || tdep->breakpoint == NULL) + { + if (BFD_ENDIAN_BIG == gdbarch_byte_order_for_code (gdbarch)) + return tic6x_bkpt_illegal_opcode_be; + else + return tic6x_bkpt_illegal_opcode_le; + } + else + return tdep->breakpoint; +} + +/* This is the implementation of gdbarch method print_insn. */ + +static int +tic6x_print_insn (bfd_vma memaddr, disassemble_info *info) +{ + return print_insn_tic6x (memaddr, info); +} + +static void +tic6x_dwarf2_frame_init_reg (struct gdbarch *gdbarch, int regnum, + struct dwarf2_frame_state_reg *reg, + struct frame_info *this_frame) +{ + /* Mark the PC as the destination for the return address. */ + if (regnum == gdbarch_pc_regnum (gdbarch)) + reg->how = DWARF2_FRAME_REG_RA; + + /* Mark the stack pointer as the call frame address. */ + else if (regnum == gdbarch_sp_regnum (gdbarch)) + reg->how = DWARF2_FRAME_REG_CFA; + + /* The above was taken from the default init_reg in dwarf2-frame.c + while the below is c6x specific. */ + + /* Callee save registers. The ABI designates A10-A15 and B10-B15 as + callee-save. */ + else if ((regnum >= 10 && regnum <= 15) || (regnum >= 26 && regnum <= 31)) + reg->how = DWARF2_FRAME_REG_SAME_VALUE; + else + /* All other registers are caller-save. */ + reg->how = DWARF2_FRAME_REG_UNDEFINED; +} + +/* This is the implementation of gdbarch method unwind_pc. */ + +static CORE_ADDR +tic6x_unwind_pc (struct gdbarch *gdbarch, struct frame_info *next_frame) +{ + gdb_byte buf[8]; + + frame_unwind_register (next_frame, TIC6X_PC_REGNUM, buf); + return extract_typed_address (buf, builtin_type (gdbarch)->builtin_func_ptr); +} + +/* This is the implementation of gdbarch method unwind_sp. */ + +static CORE_ADDR +tic6x_unwind_sp (struct gdbarch *gdbarch, struct frame_info *this_frame) +{ + return frame_unwind_register_unsigned (this_frame, TIC6X_SP_REGNUM); +} + + +/* Frame base handling. */ + +struct tic6x_unwind_cache* +tic6x_frame_unwind_cache (struct frame_info *this_frame, + void **this_prologue_cache) +{ + struct gdbarch *gdbarch = get_frame_arch (this_frame); + CORE_ADDR current_pc; + struct tic6x_unwind_cache *cache; + int i; + + if (*this_prologue_cache) + return *this_prologue_cache; + + cache = FRAME_OBSTACK_ZALLOC (struct tic6x_unwind_cache); + (*this_prologue_cache) = cache; + + cache->return_regnum = TIC6X_RA_REGNUM; + + tic6x_setup_default (cache); + + cache->pc = get_frame_func (this_frame); + current_pc = get_frame_pc (this_frame); + + /* Prologue analysis does the rest... */ + if (cache->pc != 0) + tic6x_analyze_prologue (gdbarch, cache->pc, current_pc, cache, this_frame); + + return cache; +} + +static void +tic6x_frame_this_id (struct frame_info *this_frame, void **this_cache, + struct frame_id *this_id) +{ + struct tic6x_unwind_cache *cache = + tic6x_frame_unwind_cache (this_frame, this_cache); + + /* This marks the outermost frame. */ + if (cache->base == 0) + return; + + (*this_id) = frame_id_build (cache->cfa, cache->pc); +} + +static struct value * +tic6x_frame_prev_register (struct frame_info *this_frame, void **this_cache, + int regnum) +{ + struct tic6x_unwind_cache *cache = + tic6x_frame_unwind_cache (this_frame, this_cache); + + gdb_assert (regnum >= 0); + + /* The PC of the previous frame is stored in the RA register of + the current frame. Frob regnum so that we pull the value from + the correct place. */ + if (regnum == TIC6X_PC_REGNUM) + regnum = cache->return_regnum; + + if (regnum == TIC6X_SP_REGNUM && cache->cfa) + return frame_unwind_got_constant (this_frame, regnum, cache->cfa); + + /* If we've worked out where a register is stored then load it from + there. */ + if (regnum < TIC6X_NUM_CORE_REGS && cache->reg_saved[regnum] != -1) + return frame_unwind_got_memory (this_frame, regnum, + cache->reg_saved[regnum]); + + return frame_unwind_got_register (this_frame, regnum, regnum); +} + +static CORE_ADDR +tic6x_frame_base_address (struct frame_info *this_frame, void **this_cache) +{ + struct tic6x_unwind_cache *info + = tic6x_frame_unwind_cache (this_frame, this_cache); + return info->base; +} + +static const struct frame_unwind tic6x_frame_unwind = +{ + NORMAL_FRAME, + default_frame_unwind_stop_reason, + tic6x_frame_this_id, + tic6x_frame_prev_register, + NULL, + default_frame_sniffer +}; + +static const struct frame_base tic6x_frame_base = +{ + &tic6x_frame_unwind, + tic6x_frame_base_address, + tic6x_frame_base_address, + tic6x_frame_base_address +}; + + +static struct tic6x_unwind_cache * +tic6x_make_stub_cache (struct frame_info *this_frame) +{ + struct tic6x_unwind_cache *cache; + + cache = FRAME_OBSTACK_ZALLOC (struct tic6x_unwind_cache); + + cache->return_regnum = TIC6X_RA_REGNUM; + + tic6x_setup_default (cache); + + cache->cfa = get_frame_register_unsigned (this_frame, TIC6X_SP_REGNUM); + + return cache; +} + +static void +tic6x_stub_this_id (struct frame_info *this_frame, void **this_cache, + struct frame_id *this_id) +{ + struct tic6x_unwind_cache *cache; + + if (*this_cache == NULL) + *this_cache = tic6x_make_stub_cache (this_frame); + cache = *this_cache; + + *this_id = frame_id_build (cache->cfa, get_frame_pc (this_frame)); +} + +static int +tic6x_stub_unwind_sniffer (const struct frame_unwind *self, + struct frame_info *this_frame, + void **this_prologue_cache) +{ + CORE_ADDR addr_in_block; + + addr_in_block = get_frame_address_in_block (this_frame); + if (in_plt_section (addr_in_block, NULL)) + return 1; + + return 0; +} + +static const struct frame_unwind tic6x_stub_unwind = +{ + NORMAL_FRAME, + default_frame_unwind_stop_reason, + tic6x_stub_this_id, + tic6x_frame_prev_register, + NULL, + tic6x_stub_unwind_sniffer +}; + +/* Return the instruction on address PC. */ + +static unsigned long +tic6x_fetch_instruction (struct gdbarch *gdbarch, CORE_ADDR pc) +{ + enum bfd_endian byte_order = gdbarch_byte_order (gdbarch); + return read_memory_unsigned_integer (pc, TIC6X_OPCODE_SIZE, byte_order); +} + +/* Compute the condition of INST if it is a conditional instruction. Always + return 1 if INST is not a conditional instruction. */ + +static int +tic6x_condition_true (struct frame_info *frame, unsigned long inst) +{ + int register_number; + int register_value; + static const int register_numbers[8] = { -1, 16, 17, 18, 1, 2, 0, -1 }; + + register_number = register_numbers[(inst >> 29) & 7]; + if (register_number == -1) + return 1; + + register_value = get_frame_register_signed (frame, register_number); + if ((inst & 0x10000000) != 0) + return register_value == 0; + return register_value != 0; +} + +/* Get the register number by decoding raw bits REG, SIDE, and CROSSPATH in + instruction. */ + +static int +tic6x_register_number (int reg, int side, int crosspath) +{ + int r = (reg & 15) | ((crosspath ^ side) << 4); + if ((reg & 16) != 0) /* A16 - A31, B16 - B31 */ + r += 37; + return r; +} + +static int +tic6x_extract_signed_field (int value, int low_bit, int bits) +{ + int mask = (1 << bits) - 1; + int r = (value >> low_bit) & mask; + if ((r & (1 << (bits - 1))) != 0) + r -= mask + 1; + return r; +} + +/* Determine where to set a single step breakpoint. */ + +static CORE_ADDR +tic6x_get_next_pc (struct frame_info *frame, CORE_ADDR pc) +{ + struct gdbarch *gdbarch = get_frame_arch (frame); + unsigned long inst; + int offset; + int register_number; + int last = 0; + + do + { + inst = tic6x_fetch_instruction (gdbarch, pc); + + last = !(inst & 1); + + if (inst == TIC6X_INST_SWE) + { + struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch); + + if (tdep->syscall_next_pc != NULL) + return tdep->syscall_next_pc (frame); + } + + if (tic6x_condition_true (frame, inst)) + { + if ((inst & 0x0000007c) == 0x00000010) + { + /* B with displacement */ + pc &= ~(TIC6X_FETCH_PACKET_SIZE - 1); + pc += tic6x_extract_signed_field (inst, 7, 21) << 2; + break; + } + if ((inst & 0x0f83effc) == 0x00000360) + { + /* B with register */ + + register_number = tic6x_register_number ((inst >> 18) & 0x1f, + INST_S_BIT (inst), + INST_X_BIT (inst)); + pc = get_frame_register_unsigned (frame, register_number); + break; + } + if ((inst & 0x00001ffc) == 0x00001020) + { + /* BDEC */ + register_number = tic6x_register_number ((inst >> 23) & 0x1f, + INST_S_BIT (inst), 0); + if (get_frame_register_signed (frame, register_number) >= 0) + { + pc &= ~(TIC6X_FETCH_PACKET_SIZE - 1); + pc += tic6x_extract_signed_field (inst, 7, 10) << 2; + } + break; + } + if ((inst & 0x00001ffc) == 0x00000120) + { + /* BNOP with displacement */ + pc &= ~(TIC6X_FETCH_PACKET_SIZE - 1); + pc += tic6x_extract_signed_field (inst, 16, 12) << 2; + break; + } + if ((inst & 0x0f830ffe) == 0x00800362) + { + /* BNOP with register */ + register_number = tic6x_register_number ((inst >> 18) & 0x1f, + 1, INST_X_BIT (inst)); + pc = get_frame_register_unsigned (frame, register_number); + break; + } + if ((inst & 0x00001ffc) == 0x00000020) + { + /* BPOS */ + register_number = tic6x_register_number ((inst >> 23) & 0x1f, + INST_S_BIT (inst), 0); + if (get_frame_register_signed (frame, register_number) >= 0) + { + pc &= ~(TIC6X_FETCH_PACKET_SIZE - 1); + pc += tic6x_extract_signed_field (inst, 13, 10) << 2; + } + break; + } + if ((inst & 0xf000007c) == 0x10000010) + { + /* CALLP */ + pc &= ~(TIC6X_FETCH_PACKET_SIZE - 1); + pc += tic6x_extract_signed_field (inst, 7, 21) << 2; + break; + } + } + pc += TIC6X_OPCODE_SIZE; + } + while (!last); + return pc; +} + +/* This is the implementation of gdbarch method software_single_step. */ + +int +tic6x_software_single_step (struct frame_info *frame) +{ + struct gdbarch *gdbarch = get_frame_arch (frame); + struct address_space *aspace = get_frame_address_space (frame); + CORE_ADDR next_pc = tic6x_get_next_pc (frame, get_frame_pc (frame)); + + insert_single_step_breakpoint (gdbarch, aspace, next_pc); + + return 1; +} + +/* This is the implementation of gdbarch method frame_align. */ + +static CORE_ADDR +tic6x_frame_align (struct gdbarch *gdbarch, CORE_ADDR addr) +{ + return align_down (addr, 8); +} + +/* This is the implementation of gdbarch method register_to_value. */ + +static int +tic6x_register_to_value (struct frame_info *frame, int regnum, + struct type *type, gdb_byte * to, + int *optimizedp, int *unavailablep) +{ + get_frame_register (frame, regnum, (char *) to); + *optimizedp = *unavailablep = 0; + return 1; +} + +/* This is the implementation of gdbarch method value_to_register. */ + +static void +tic6x_value_to_register (struct frame_info *frame, int regnum, + struct type *type, const gdb_byte *from) +{ + put_frame_register (frame, regnum, from); +} + +/* Given a return value in REGCACHE with a type VALTYPE, extract and copy its + value into VALBUF. */ + +static void +tic6x_extract_return_value (struct type *valtype, struct regcache *regcache, + enum bfd_endian byte_order, gdb_byte *valbuf) +{ + int len = TYPE_LENGTH (valtype); + + /* pointer types are returned in register A4, + up to 32-bit types in A4 + up to 64-bit types in A5:A4 */ + if (len <= 4) + { + /* In big-endian, + - one-byte structure or union occupies the LSB of single even register. + - for two-byte structure or union, the first byte occupies byte 1 of + register and the second byte occupies byte 0. + so, we read the contents in VAL from the LSBs of register. */ + if (len < 3 && byte_order == BFD_ENDIAN_BIG) + regcache_cooked_read_part (regcache, TIC6X_A4_REGNUM, 4 - len, len, + valbuf); + else + regcache_cooked_read (regcache, TIC6X_A4_REGNUM, valbuf); + } + else if (len <= 8) + { + /* For a 5-8 byte structure or union in big-endian, the first byte + occupies byte 3 (the MSB) of the upper (odd) register and the + remaining bytes fill the decreasingly significant bytes. 5-7 + byte structures or unions have padding in the LSBs of the + lower (even) register. */ + if (byte_order == BFD_ENDIAN_BIG) + { + regcache_cooked_read (regcache, TIC6X_A4_REGNUM, valbuf + 4); + regcache_cooked_read (regcache, TIC6X_A5_REGNUM, valbuf); + } + else + { + regcache_cooked_read (regcache, TIC6X_A4_REGNUM, valbuf); + regcache_cooked_read (regcache, TIC6X_A5_REGNUM, valbuf + 4); + } + } +} + +/* Write into appropriate registers a function return value + of type TYPE, given in virtual format. */ + +static void +tic6x_store_return_value (struct type *valtype, struct regcache *regcache, + enum bfd_endian byte_order, const gdb_byte *valbuf) +{ + int len = TYPE_LENGTH (valtype); + + /* return values of up to 8 bytes are returned in A5:A4 */ + + if (len <= 4) + { + if (len < 3 && byte_order == BFD_ENDIAN_BIG) + regcache_cooked_write_part (regcache, TIC6X_A4_REGNUM, 4 - len, len, + valbuf); + else + regcache_cooked_write (regcache, TIC6X_A4_REGNUM, valbuf); + } + else if (len <= 8) + { + if (byte_order == BFD_ENDIAN_BIG) + { + regcache_cooked_write (regcache, TIC6X_A4_REGNUM, valbuf + 4); + regcache_cooked_write (regcache, TIC6X_A5_REGNUM, valbuf); + } + else + { + regcache_cooked_write (regcache, TIC6X_A4_REGNUM, valbuf); + regcache_cooked_write (regcache, TIC6X_A5_REGNUM, valbuf + 4); + } + } +} + +/* This is the implementation of gdbarch method return_value. */ + +static enum return_value_convention +tic6x_return_value (struct gdbarch *gdbarch, struct type *func_type, + struct type *type, struct regcache *regcache, + gdb_byte *readbuf, const gdb_byte *writebuf) +{ + if (TYPE_LENGTH (type) > 8) + return RETURN_VALUE_STRUCT_CONVENTION; + + if (readbuf) + tic6x_extract_return_value (type, regcache, + gdbarch_byte_order (gdbarch), readbuf); + if (writebuf) + tic6x_store_return_value (type, regcache, + gdbarch_byte_order (gdbarch), writebuf); + + return RETURN_VALUE_REGISTER_CONVENTION; +} + +/* This is the implementation of gdbarch method dummy_id. */ + +static struct frame_id +tic6x_dummy_id (struct gdbarch *gdbarch, struct frame_info *this_frame) +{ + return frame_id_build + (get_frame_register_unsigned (this_frame, TIC6X_SP_REGNUM), + get_frame_pc (this_frame)); +} + +/* Get the alignment requirement of TYPE. */ + +static int +tic6x_arg_type_alignment (struct type *type) +{ + int len = TYPE_LENGTH (check_typedef (type)); + enum type_code typecode = TYPE_CODE (check_typedef (type)); + + if (typecode == TYPE_CODE_STRUCT || typecode == TYPE_CODE_UNION) + { + /* The stack alignment of a structure (and union) passed by value is the + smallest power of two greater than or equal to its size. + This cannot exceed 8 bytes, which is the largest allowable size for + a structure passed by value. */ + + if (len <= 2) + return len; + else if (len <= 4) + return 4; + else if (len <= 8) + return 8; + else + gdb_assert_not_reached ("unexpected length of data"); + } + else + { + if (len <= 4) + return 4; + else if (len == 8) + { + if (typecode == TYPE_CODE_COMPLEX) + return 4; + else + return 8; + } + else if (len == 16) + { + if (typecode == TYPE_CODE_COMPLEX) + return 8; + else + return 16; + } + else + internal_error (__FILE__, __LINE__, _("unexpected length %d of type"), + len); + } +} + +/* This is the implementation of gdbarch method push_dummy_call. */ + +static CORE_ADDR +tic6x_push_dummy_call (struct gdbarch *gdbarch, struct value *function, + struct regcache *regcache, CORE_ADDR bp_addr, + int nargs, struct value **args, CORE_ADDR sp, + int struct_return, CORE_ADDR struct_addr) +{ + int argreg = 0; + int argnum; + int len = 0; + int stack_offset = 4; + int references_offset = 4; + CORE_ADDR func_addr = find_function_addr (function, NULL); + enum bfd_endian byte_order = gdbarch_byte_order (gdbarch); + struct type *func_type = value_type (function); + /* The first arg passed on stack. Mostly the first 10 args are passed by + registers. */ + int first_arg_on_stack = 10; + /* If this inf-call is a cpp method call, and return value is passed by + reference, this flag is set to 1, otherwise set to 0. We need this flag + because computation of the return location in + infcall.c:call_function_by_hand is wrong for C6000 ELF ABI. In + call_function_by_hand, the language is considered first, and then + target ABI is considered. If language_pass_by_reference returns true, + the return location is passed as the first parameter to the function, + which is conflict with C6000 ELF ABI. If this flag is true, we should + adjust args and return locations accordingly to comply with C6000 ELF + ABI. */ + int cplus_return_struct_by_reference = 0; + + if (current_language->la_language == language_cplus) + { + struct type *values_type; + + find_function_addr (function, &values_type); + + if (values_type) + { + CHECK_TYPEDEF (values_type); + if (language_pass_by_reference (values_type)) + cplus_return_struct_by_reference = 1; + } + + } + /* Set the return address register to point to the entry point of + the program, where a breakpoint lies in wait. */ + regcache_cooked_write_unsigned (regcache, TIC6X_RA_REGNUM, bp_addr); + + /* The caller must pass an argument in A3 containing a destination address + for the returned value. The callee returns the object by copying it to + the address in A3. */ + if (struct_return) + regcache_cooked_write_unsigned (regcache, 3, struct_addr); + else if (cplus_return_struct_by_reference) + /* When cplus_return_struct_by_reference is 1, means local variable + lang_struct_return in call_function_by_hand is 1, so struct is + returned by reference, even STRUCT_RETURN is 0. Note that STRUCT_ADDR + is still valid in this case. */ + regcache_cooked_write_unsigned (regcache, 3, struct_addr); + + /* Determine the type of this function. */ + func_type = check_typedef (func_type); + if (TYPE_CODE (func_type) == TYPE_CODE_PTR) + func_type = check_typedef (TYPE_TARGET_TYPE (func_type)); + + gdb_assert (TYPE_CODE (func_type) == TYPE_CODE_FUNC + || TYPE_CODE (func_type) == TYPE_CODE_METHOD); + + /* For a variadic C function, the last explicitly declared argument and all + remaining arguments are passed on the stack. */ + if (TYPE_VARARGS (func_type)) + first_arg_on_stack = TYPE_NFIELDS (func_type) - 1; + + /* Now make space on the stack for the args. If + cplus_return_struct_by_reference is 1, means GDB pass an extra parameter + in ARGS, which is useless here, skip it. */ + for (argnum = cplus_return_struct_by_reference; argnum < nargs; argnum++) + { + int len = align_up (TYPE_LENGTH (value_type (args[argnum])), 4); + if (argnum >= 10 - argreg) + references_offset += len; + stack_offset += len; + } + sp -= stack_offset; + /* SP should be 8-byte aligned, see C6000 ABI section 4.4.1 + Stack Alignment. */ + sp = align_down (sp, 8); + stack_offset = 4; + + /* Now load as many as possible of the first arguments into + registers, and push the rest onto the stack. Loop through args + from first to last. */ + for (argnum = cplus_return_struct_by_reference; argnum < nargs; argnum++) + { + const gdb_byte *val; + struct value *arg = args[argnum]; + struct type *arg_type = check_typedef (value_type (arg)); + int len = TYPE_LENGTH (arg_type); + enum type_code typecode = TYPE_CODE (arg_type); + + val = value_contents (arg); + + /* Copy the argument to general registers or the stack in + register-sized pieces. */ + if (argreg < first_arg_on_stack) + { + if (len <= 4) + { + if (typecode == TYPE_CODE_STRUCT || typecode == TYPE_CODE_UNION) + { + /* In big-endian, + - one-byte structure or union occupies the LSB of single + even register. + - for two-byte structure or union, the first byte + occupies byte 1 of register and the second byte occupies + byte 0. + so, we write the contents in VAL to the lsp of + register. */ + if (len < 3 && byte_order == BFD_ENDIAN_BIG) + regcache_cooked_write_part (regcache, arg_regs[argreg], + 4 - len, len, val); + else + regcache_cooked_write (regcache, arg_regs[argreg], val); + } + else + { + /* The argument is being passed by value in a single + register. */ + CORE_ADDR regval = extract_unsigned_integer (val, len, + byte_order); + + regcache_cooked_write_unsigned (regcache, arg_regs[argreg], + regval); + } + } + else + { + if (len <= 8) + { + if (typecode == TYPE_CODE_STRUCT + || typecode == TYPE_CODE_UNION) + { + /* For a 5-8 byte structure or union in big-endian, the + first byte occupies byte 3 (the MSB) of the upper (odd) + register and the remaining bytes fill the decreasingly + significant bytes. 5-7 byte structures or unions have + padding in the LSBs of the lower (even) register. */ + if (byte_order == BFD_ENDIAN_BIG) + { + regcache_cooked_write (regcache, + arg_regs[argreg] + 1, val); + regcache_cooked_write_part (regcache, + arg_regs[argreg], 0, + len - 4, val + 4); + } + else + { + regcache_cooked_write (regcache, arg_regs[argreg], + val); + regcache_cooked_write_part (regcache, + arg_regs[argreg] + 1, 0, + len - 4, val + 4); + } + } + else + { + /* The argument is being passed by value in a pair of + registers. */ + ULONGEST regval = extract_unsigned_integer (val, len, + byte_order); + + regcache_cooked_write_unsigned (regcache, + arg_regs[argreg], + regval); + regcache_cooked_write_unsigned (regcache, + arg_regs[argreg] + 1, + regval >> 32); + } + } + else + { + /* The argument is being passed by reference in a single + register. */ + CORE_ADDR addr; + + /* It is not necessary to adjust REFERENCES_OFFSET to + 8-byte aligned in some cases, in which 4-byte alignment + is sufficient. For simplicity, we adjust + REFERENCES_OFFSET to 8-byte aligned. */ + references_offset = align_up (references_offset, 8); + + addr = sp + references_offset; + write_memory (addr, val, len); + references_offset += align_up (len, 4); + regcache_cooked_write_unsigned (regcache, arg_regs[argreg], + addr); + } + } + argreg++; + } + else + { + /* The argument is being passed on the stack. */ + CORE_ADDR addr; + + /* There are six different cases of alignment, and these rules can + be found in tic6x_arg_type_alignment: + + 1) 4-byte aligned if size is less than or equal to 4 byte, such + as short, int, struct, union etc. + 2) 8-byte aligned if size is less than or equal to 8-byte, such + as double, long long, + 3) 4-byte aligned if it is of type _Complex float, even its size + is 8-byte. + 4) 8-byte aligned if it is of type _Complex double or _Complex + long double, even its size is 16-byte. Because, the address of + variable is passed as reference. + 5) struct and union larger than 8-byte are passed by reference, so + it is 4-byte aligned. + 6) struct and union of size between 4 byte and 8 byte varies. + alignment of struct variable is the alignment of its first field, + while alignment of union variable is the max of all its fields' + alignment. */ + + if (len <= 4) + ; /* Default is 4-byte aligned. Nothing to be done. */ + else if (len <= 8) + stack_offset = align_up (stack_offset, + tic6x_arg_type_alignment (arg_type)); + else if (len == 16) + { + /* _Complex double or _Complex long double */ + if (typecode == TYPE_CODE_COMPLEX) + { + /* The argument is being passed by reference on stack. */ + CORE_ADDR addr; + references_offset = align_up (references_offset, 8); + + addr = sp + references_offset; + /* Store variable on stack. */ + write_memory (addr, val, len); + + references_offset += align_up (len, 4); + + /* Pass the address of variable on stack as reference. */ + store_unsigned_integer ((gdb_byte *) val, 4, byte_order, + addr); + len = 4; + + } + else + internal_error (__FILE__, __LINE__, + _("unexpected type %d of arg %d"), + typecode, argnum); + } + else + internal_error (__FILE__, __LINE__, + _("unexpected length %d of arg %d"), len, argnum); + + addr = sp + stack_offset; + write_memory (addr, val, len); + stack_offset += align_up (len, 4); + } + } + + regcache_cooked_write_signed (regcache, TIC6X_SP_REGNUM, sp); + + /* Return adjusted stack pointer. */ + return sp; +} + +/* This is the implementation of gdbarch method in_function_epilogue_p. */ + +static int +tic6x_in_function_epilogue_p (struct gdbarch *gdbarch, CORE_ADDR pc) +{ + unsigned long inst = tic6x_fetch_instruction (gdbarch, pc); + /* Normally, the epilogue is composed by instruction `b .S2 b3'. */ + if ((inst & 0x0f83effc) == 0x360) + { + unsigned int src2 = tic6x_register_number ((inst >> 18) & 0x1f, + INST_S_BIT (inst), + INST_X_BIT (inst)); + if (src2 == TIC6X_RA_REGNUM) + return 1; + } + + return 0; +} + +/* This is the implementation of gdbarch method get_longjmp_target. */ + +static int +tic6x_get_longjmp_target (struct frame_info *frame, CORE_ADDR *pc) +{ + struct gdbarch *gdbarch = get_frame_arch (frame); + enum bfd_endian byte_order = gdbarch_byte_order (gdbarch); + CORE_ADDR jb_addr; + char buf[4]; + + /* JMP_BUF is passed by reference in A4. */ + jb_addr = get_frame_register_unsigned (frame, 4); + + /* JMP_BUF contains 13 elements of type int, and return address is stored + in the last slot. */ + if (target_read_memory (jb_addr + 12 * 4, buf, 4)) + return 0; + + *pc = extract_unsigned_integer (buf, 4, byte_order); + + return 1; +} + +static struct gdbarch * +tic6x_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches) +{ + struct gdbarch *gdbarch; + struct gdbarch_tdep *tdep; + struct tdesc_arch_data *tdesc_data = NULL; + const struct target_desc *tdesc = info.target_desc; + int has_gp = 0; + + /* Check any target description for validity. */ + if (tdesc_has_registers (tdesc)) + { + const struct tdesc_feature *feature; + int valid_p, i; + + feature = tdesc_find_feature (tdesc, "org.gnu.gdb.tic6x.core"); + + if (feature == NULL) + return NULL; + + tdesc_data = tdesc_data_alloc (); + + valid_p = 1; + for (i = 0; i < 32; i++) /* A0 - A15, B0 - B15 */ + valid_p &= tdesc_numbered_register (feature, tdesc_data, i, + tic6x_register_names[i]); + + /* CSR */ + valid_p &= tdesc_numbered_register (feature, tdesc_data, i++, + tic6x_register_names[TIC6X_CSR_REGNUM]); + valid_p &= tdesc_numbered_register (feature, tdesc_data, i++, + tic6x_register_names[TIC6X_PC_REGNUM]); + + if (!valid_p) + { + tdesc_data_cleanup (tdesc_data); + return NULL; + } + + feature = tdesc_find_feature (tdesc, "org.gnu.gdb.tic6x.gp"); + if (feature) + { + int j = 0; + static const char *const gp[] = + { + "A16", "A17", "A18", "A19", "A20", "A21", "A22", "A23", + "A24", "A25", "A26", "A27", "A28", "A29", "A30", "A31", + "B16", "B17", "B18", "B19", "B20", "B21", "B22", "B23", + "B24", "B25", "B26", "B27", "B28", "B29", "B30", "B31", + }; + + has_gp = 1; + valid_p = 1; + for (j = 0; j < 32; j++) /* A16 - A31, B16 - B31 */ + valid_p &= tdesc_numbered_register (feature, tdesc_data, i++, + gp[j]); + + if (!valid_p) + { + tdesc_data_cleanup (tdesc_data); + return NULL; + } + } + + feature = tdesc_find_feature (tdesc, "org.gnu.gdb.tic6x.c6xp"); + if (feature) + { + valid_p &= tdesc_numbered_register (feature, tdesc_data, i++, "TSR"); + valid_p &= tdesc_numbered_register (feature, tdesc_data, i++, "ILC"); + valid_p &= tdesc_numbered_register (feature, tdesc_data, i++, "RILC"); + + if (!valid_p) + { + tdesc_data_cleanup (tdesc_data); + return NULL; + } + } + + } + + /* Find a candidate among extant architectures. */ + for (arches = gdbarch_list_lookup_by_info (arches, &info); + arches != NULL; + arches = gdbarch_list_lookup_by_info (arches->next, &info)) + { + tdep = gdbarch_tdep (arches->gdbarch); + + if (has_gp != tdep->has_gp) + continue; + + if (tdep && tdep->breakpoint) + return arches->gdbarch; + } + + tdep = xcalloc (1, sizeof (struct gdbarch_tdep)); + + tdep->has_gp = has_gp; + gdbarch = gdbarch_alloc (&info, tdep); + + /* Data type sizes. */ + set_gdbarch_ptr_bit (gdbarch, 32); + set_gdbarch_addr_bit (gdbarch, 32); + set_gdbarch_short_bit (gdbarch, 16); + set_gdbarch_int_bit (gdbarch, 32); + set_gdbarch_long_bit (gdbarch, 32); + set_gdbarch_long_long_bit (gdbarch, 64); + set_gdbarch_float_bit (gdbarch, 32); + set_gdbarch_double_bit (gdbarch, 64); + + set_gdbarch_float_format (gdbarch, floatformats_ieee_single); + set_gdbarch_double_format (gdbarch, floatformats_ieee_double); + + /* The register set. */ + set_gdbarch_num_regs (gdbarch, TIC6X_NUM_REGS); + set_gdbarch_sp_regnum (gdbarch, TIC6X_SP_REGNUM); + set_gdbarch_pc_regnum (gdbarch, TIC6X_PC_REGNUM); + + set_gdbarch_register_name (gdbarch, tic6x_register_name); + set_gdbarch_register_type (gdbarch, tic6x_register_type); + + set_gdbarch_inner_than (gdbarch, core_addr_lessthan); + + set_gdbarch_skip_prologue (gdbarch, tic6x_skip_prologue); + set_gdbarch_breakpoint_from_pc (gdbarch, tic6x_breakpoint_from_pc); + + set_gdbarch_unwind_pc (gdbarch, tic6x_unwind_pc); + set_gdbarch_unwind_sp (gdbarch, tic6x_unwind_sp); + + /* Unwinding. */ + dwarf2_append_unwinders (gdbarch); + + frame_unwind_append_unwinder (gdbarch, &tic6x_stub_unwind); + frame_unwind_append_unwinder (gdbarch, &tic6x_frame_unwind); + + dwarf2_frame_set_init_reg (gdbarch, tic6x_dwarf2_frame_init_reg); + + /* Single stepping. */ + set_gdbarch_software_single_step (gdbarch, tic6x_software_single_step); + + set_gdbarch_print_insn (gdbarch, tic6x_print_insn); + + /* Call dummy code. */ + set_gdbarch_frame_align (gdbarch, tic6x_frame_align); + + set_gdbarch_register_to_value (gdbarch, tic6x_register_to_value); + set_gdbarch_value_to_register (gdbarch, tic6x_value_to_register); + + set_gdbarch_return_value (gdbarch, tic6x_return_value); + + set_gdbarch_dummy_id (gdbarch, tic6x_dummy_id); + + /* Enable inferior call support. */ + set_gdbarch_push_dummy_call (gdbarch, tic6x_push_dummy_call); + + set_gdbarch_get_longjmp_target (gdbarch, tic6x_get_longjmp_target); + + set_gdbarch_in_function_epilogue_p (gdbarch, tic6x_in_function_epilogue_p); + + /* Hook in ABI-specific overrides, if they have been registered. */ + gdbarch_init_osabi (info, gdbarch); + + if (tdesc_data) + tdesc_use_registers (gdbarch, tdesc, tdesc_data); + + return gdbarch; +} + +void +_initialize_tic6x_tdep (void) +{ + register_gdbarch_init (bfd_arch_tic6x, tic6x_gdbarch_init); + + initialize_tdesc_tic6x_c64xp (); + initialize_tdesc_tic6x_c64x (); + initialize_tdesc_tic6x_c62x (); +} diff --git a/gdb/tic6x-tdep.h b/gdb/tic6x-tdep.h new file mode 100644 index 00000000000..022757227f1 --- /dev/null +++ b/gdb/tic6x-tdep.h @@ -0,0 +1,54 @@ +/* GNU/Linux on TI C6x target support. + Copyright (C) 2011 + Free Software Foundation, Inc. + Contributed by Yao Qi + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +enum +{ + TIC6X_A4_REGNUM = 4, + TIC6X_A5_REGNUM = 5, + TIC6X_FP_REGNUM = 15, /* Frame Pointer: A15 */ + TIC6X_B0_REGNUM = 16, + TIC6X_RA_REGNUM = 19, /* Return address: B3 */ + TIC6X_B4_REGNUM = 20, + TIC6X_B5_REGNUM = 21, + TIC6X_DP_REGNUM = 30, /* Data Page Pointer: B14 */ + TIC6X_SP_REGNUM = 31, /* Stack Pointer: B15 */ + TIC6X_CSR_REGNUM = 32, + TIC6X_PC_REGNUM = 33, + TIC6X_NUM_CORE_REGS = 33, /* The number of core registers */ + TIC6X_RILC_REGNUM = 68, + TIC6X_NUM_REGS /* The number of registers */ +}; + +#define TIC6X_INST_SWE 0x10000000 + +extern const gdb_byte tic6x_bkpt_illegal_opcode_be[]; +extern const gdb_byte tic6x_bkpt_illegal_opcode_le[]; + +/* Target-dependent structure in gdbarch. */ +struct gdbarch_tdep +{ + /* Return the expected next PC if FRAME is stopped at a syscall + instruction. */ + CORE_ADDR (*syscall_next_pc) (struct frame_info *frame); + + const char *breakpoint; /* Breakpoint instruction. */ + + int has_gp; /* Has general purpose registers A16 - A31 and B16 - B31. */ +};