+
+/* Linker stubs.
+ The stubs will be gathered in stub csects named "@FIX'number'".
+ A new csect will be created by xcoff_stub_get_csect_in_range,
+ everytime a relocation cannot reach its target and its section
+ is too far from the others stub csects.
+ The stubs will simply be code generated inside these stub
+ csects. In order to simplify the symbol table, only the symbols
+ for the stub csects are written.
+
+ As the code is dependent of the architecture, it's defined
+ in the backend.
+
+ xcoff_stub_indirect_call:
+ Used when a 24 bit branch cannot reach its destination and that
+ this destination isn't a global linkage symbol.
+
+ xcoff_stub_shared_call:
+ As above but when it's a global linkage symbol.
+ The main difference being that it doesn't branch to the global
+ linkage symbol which will then call the shared library. It
+ directly call it saving the TOC.
+
+ TODO: -bbigtoc option should be able to be implemented using
+ this stubs. */
+
+/* Get the name of a csect which will contain stubs.
+ It has the same pattern as AIX linker: @FIX"number". */
+static char *
+xcoff_stub_csect_name (unsigned int n)
+{
+ char buf[8];
+ size_t len;
+ char *csect_name;
+
+ /* For now, allow "only" 1000000 stub csects. */
+ if (n >= 1000000)
+ {
+ BFD_FAIL();
+ return NULL;
+ }
+
+ sprintf (buf, "%d", n);
+ len = 4 + strlen (buf) + 1;
+
+ csect_name = bfd_malloc (len);
+ if (csect_name == NULL)
+ return NULL;
+ sprintf (csect_name, "@FIX%d", n);
+
+ return csect_name;
+}
+
+/* Return a stub section which can be reach with a single branch
+ from SECTION. CREATE means that creating a csect is allowed. */
+static struct xcoff_link_hash_entry *
+xcoff_stub_get_csect_in_range (asection *section,
+ struct bfd_link_info *info,
+ bool create)
+{
+ struct xcoff_link_hash_table *htab = xcoff_hash_table (info);
+ struct xcoff_link_hash_entry *csect_entry;
+ struct bfd_link_hash_entry *bh = NULL;
+ asection *csect;
+ unsigned int it;
+ char *csect_name;
+
+ /* Search for a csect in range. */
+ for (csect = htab->params->stub_bfd->sections, it = 0;
+ csect != NULL;
+ csect = csect->next, it++)
+ {
+ /* A csect is in range if everything instructions in SECTION
+ can branch to every stubs in the stub csect. This can
+ be simplify by saying that the first entry of each sections
+ (ie the vma of this section) can reach the last entry of the
+ stub csect (ie the vma of the csect + its size).
+ However, as the stub csect might be growing its size isn't
+ fixed. Thus, the last entry of SECTION might not be able
+ to reach the first entry of the stub csect anymore.
+ If this case happens, the following condition will be
+ false during the next pass of bfd_xcoff_size_stubs and
+ another csect will be used.
+ This means we might create more stubs than needed. */
+ bfd_vma csect_vma, section_vma;
+ bfd_vma csect_last_vma, section_last_vma;
+
+ csect_vma = (csect->output_section->vma
+ + csect->output_offset);
+ csect_last_vma = (csect->output_section->vma
+ + csect->output_offset
+ + csect->size);
+ section_vma = (section->output_section->vma
+ + section->output_offset);
+ section_last_vma = (section->output_section->vma
+ + section->output_offset
+ + section->size);
+
+ if (csect_last_vma - section_vma + (1 << 25) < 2 * (1 << 25)
+ && section_last_vma - csect_vma + (1 << 25) < 2 * (1 << 25))
+ break;
+ }
+
+ if (!create && csect == NULL)
+ return NULL;
+
+ csect_name = xcoff_stub_csect_name (it);
+ if (!csect_name)
+ return NULL;
+
+ /* A stub csect already exists, get its entry. */
+ if (csect != NULL)
+ {
+ csect_entry = xcoff_link_hash_lookup (htab, csect_name, false, false, true);
+ free(csect_name);
+ return csect_entry;
+ }
+
+ /* Create the csect and its symbol. */
+ csect = (*htab->params->add_stub_section) (".pr", section);
+ if (!csect)
+ {
+ free(csect_name);
+ return NULL;
+ }
+
+ csect->alignment_power = 2;
+ csect->gc_mark = 1;
+ csect->reloc_count = 0;
+
+ /* We need to associate a VMA to this new csect. Otherwise,
+ our "in range" algorithm won't find it for the next stub.
+ And as we will be adding this stub section just after the
+ SECTION, we know its address. */
+ csect->output_offset = BFD_ALIGN (section->output_offset + section->size,
+ 4);
+
+ if (!_bfd_generic_link_add_one_symbol (info, htab->params->stub_bfd,
+ csect_name, BSF_GLOBAL, csect, 0,
+ NULL, true, true, &bh))
+ {
+ free(csect_name);
+ return NULL;
+ }
+
+ csect_entry = (struct xcoff_link_hash_entry *)bh;
+ csect_entry->smclas = XMC_PR;
+ csect_entry->flags = XCOFF_MARK | XCOFF_DEF_REGULAR;
+
+ free(csect_name);
+ return csect_entry;
+}
+
+
+/* Build a name for an entry in the stub hash table. */
+static char *
+xcoff_stub_name (const struct xcoff_link_hash_entry *h,
+ const struct xcoff_link_hash_entry *hcsect)
+{
+ char *stub_name;
+ size_t len;
+
+ if (h)
+ {
+ /* The name of a stub is based on its stub csect and the
+ symbol it wants to reach. It looks like: ".@FIX0.tramp.f".
+ When the stub targets a function, the last dot of ".tramp."
+ is removed to avoid having two dot. */
+ len = (1 + 6
+ + strlen (hcsect->root.root.string)
+ + strlen (h->root.root.string)
+ + 1);
+ if (h->root.root.string[0] != '.')
+ len++;
+
+ stub_name = bfd_malloc (len);
+ if (stub_name == NULL)
+ return stub_name;
+
+ if (h->root.root.string[0] == '.')
+ sprintf (stub_name, ".%s.tramp%s",
+ hcsect->root.root.string,
+ h->root.root.string);
+ else
+ sprintf (stub_name, ".%s.tramp.%s",
+ hcsect->root.root.string,
+ h->root.root.string);
+ }
+ else
+ {
+ BFD_FAIL();
+ return NULL;
+ }
+
+ return stub_name;
+}
+
+/* Look up an entry in the stub hash. */
+struct xcoff_stub_hash_entry *
+bfd_xcoff_get_stub_entry (asection *section,
+ struct xcoff_link_hash_entry *h,
+ struct bfd_link_info *info)
+{
+ struct xcoff_link_hash_table *htab = xcoff_hash_table (info);
+ struct xcoff_link_hash_entry *hcsect;
+ struct xcoff_stub_hash_entry *hstub;
+ char *stub_name;
+
+ hcsect = xcoff_stub_get_csect_in_range (section, info, false);
+ if (!hcsect)
+ return NULL;
+
+ stub_name = xcoff_stub_name (h, hcsect);
+ if (stub_name == NULL)
+ return NULL;
+
+ hstub = xcoff_stub_hash_lookup (&htab->stub_hash_table,
+ stub_name, false, false);
+
+ free (stub_name);
+ return hstub;
+}
+
+/* Check if the symbol targeted by IREL is reachable.
+ Return the type of stub needed otherwise. */
+enum xcoff_stub_type
+bfd_xcoff_type_of_stub (asection *sec,
+ const struct internal_reloc *irel,
+ bfd_vma destination,
+ struct xcoff_link_hash_entry *h)
+{
+ bfd_vma location, offset, max_offset;
+
+ switch (irel->r_type)
+ {
+ default:
+ return xcoff_stub_none;
+
+ case R_BR:
+ case R_RBR:
+ location = (sec->output_section->vma
+ + sec->output_offset
+ + irel->r_vaddr
+ - sec->vma);
+
+ max_offset = 1 << 25 ;
+
+ offset = destination - location;
+
+ if (offset + max_offset < 2 * max_offset)
+ return xcoff_stub_none;
+
+ /* A stub is needed. Now, check that we can make one. */
+ if (h != NULL
+ && h->descriptor != NULL)
+ {
+ /* Not sure how to handle this case. For now, skip it. */
+ if (bfd_is_abs_section (h->root.u.def.section))
+ return xcoff_stub_none;
+
+ if (h->smclas == XMC_GL)
+ return xcoff_stub_shared_call;
+ else
+ return xcoff_stub_indirect_call;
+ }
+ break;
+ }
+
+ return xcoff_stub_none;
+}
+
+/* Add a new stub entry to the stub hash. Not all fields of the new
+ stub entry are initialised. */
+static struct xcoff_stub_hash_entry *
+xcoff_add_stub (const char *stub_name,
+ struct xcoff_link_hash_entry *hstub_csect,
+ struct xcoff_link_hash_entry *htarget,
+ struct bfd_link_info *info,
+ enum xcoff_stub_type stub_type)
+{
+ struct xcoff_link_hash_table *htab = xcoff_hash_table (info);
+ struct xcoff_stub_hash_entry *hstub;
+ bfd_vma stub_offset;
+ asection *stub_csect;
+
+ stub_csect = hstub_csect->root.u.def.section;
+ stub_offset = stub_csect->size;
+
+ /* Update the relocation counter and the size of
+ the containing csect. The size is needed for
+ the algorithm in xcoff_stub_get_csect_in_range. */
+ switch (stub_type)
+ {
+ default:
+ BFD_FAIL ();
+ return NULL;
+
+ case xcoff_stub_indirect_call:
+ stub_csect->reloc_count++;
+ stub_csect->size += bfd_xcoff_stub_indirect_call_size (info->output_bfd);
+ break;
+
+ case xcoff_stub_shared_call:
+ stub_csect->reloc_count++;
+ stub_csect->size += bfd_xcoff_stub_shared_call_size (info->output_bfd);
+ break;
+ }
+
+ /* Create the stub entry. */
+ hstub = xcoff_stub_hash_lookup (&htab->stub_hash_table, stub_name,
+ true, true);
+ if (hstub == NULL)
+ return NULL;
+
+ hstub->htarget = htarget;
+ hstub->stub_offset = stub_offset;
+
+ /* For indirect call or shared call, the relocations are against
+ the target descriptor. Its toc entry will be used. */
+ if (stub_type == xcoff_stub_indirect_call
+ || stub_type == xcoff_stub_shared_call)
+ {
+ struct xcoff_link_hash_entry *hds = htarget->descriptor;
+ asection *hds_section = hds->root.u.def.section;
+
+ hstub->htarget = hds;
+
+ /* If the symbol haven't been marked, its section might have
+ its size and its relocation count been deleted by xcoff_sweep.
+ Restore it. */
+ if ((hds->flags & XCOFF_MARK) == 0)
+ {
+ if (hds_section->size == 0
+ && hds_section->reloc_count == 0
+ && hds_section->rawsize != 0)
+ {
+ hds_section->size = hds_section->rawsize;
+ /* Always two relocations for a XMC_DS symbol. */
+ hds_section->reloc_count = 2;
+ }
+
+ /* Mark the section and the symbol. */
+ if (!xcoff_mark (info, hds_section))
+ return NULL;
+ }
+
+ /* Add a TOC entry for the descriptor if non exists. */
+ if (hds->toc_section == NULL)
+ {
+ int byte_size;
+
+ if (bfd_xcoff_is_xcoff64 (info->output_bfd))
+ byte_size = 8;
+ else if (bfd_xcoff_is_xcoff32 (info->output_bfd))
+ byte_size = 4;
+ else
+ return NULL;
+
+ /* Allocate room in the fallback TOC section. */
+ hds->toc_section = xcoff_hash_table (info)->toc_section;
+ hds->u.toc_offset = hds->toc_section->size;
+ hds->toc_section->size += byte_size;
+ if (!xcoff_mark (info, hds->toc_section))
+ return NULL;
+
+ /* Update relocation counters for a static and dynamic
+ R_TOC relocation. */
+ ++hds->toc_section->reloc_count;
+ ++htab->ldinfo.ldrel_count;
+
+ /* Set the index to -2 to force this symbol to
+ get written out. */
+ hds->indx = -2;
+ hds->flags |= XCOFF_SET_TOC;
+ }
+ }
+
+ return hstub;
+}
+
+static bool
+xcoff_build_one_stub (struct bfd_hash_entry *gen_entry, void *in_arg)
+{
+ struct xcoff_stub_hash_entry *hstub
+ = (struct xcoff_stub_hash_entry *) gen_entry;
+
+ bfd *stub_bfd;
+ bfd *output_bfd;
+ struct bfd_link_info *info;
+ bfd_byte *loc;
+ bfd_byte *p;
+ unsigned int i;
+
+ info = (struct bfd_link_info *) in_arg;
+ stub_bfd = xcoff_hash_table (info)->params->stub_bfd;
+ output_bfd = info->output_bfd;
+
+ /* Fail if the target section could not be assigned to an output
+ section. The user should fix his linker script. */
+ if (hstub->target_section != NULL
+ && hstub->target_section->output_section == NULL
+ && info->non_contiguous_regions)
+ info->callbacks->einfo (_("%F%P: Could not assign '%pA' to an output section. "
+ "Retry without --enable-non-contiguous-regions.\n"),
+ hstub->target_section);
+
+ loc = (hstub->hcsect->root.u.def.section->contents
+ + hstub->stub_offset);
+ p = loc;
+
+ switch (hstub->stub_type)
+ {
+ case xcoff_stub_indirect_call:
+ BFD_ASSERT (hstub->htarget->toc_section != NULL);
+ /* The first instruction in the stub code needs to be
+ cooked to hold the correct offset in the toc. It will
+ be filled by xcoff_stub_create_relocations. */
+ for (i = 0; i < bfd_xcoff_stub_indirect_call_size(output_bfd) / 4; i++)
+ bfd_put_32 (stub_bfd,
+ (bfd_vma) bfd_xcoff_stub_indirect_call_code(output_bfd, i),
+ &p[4 * i]);
+ break;
+
+ case xcoff_stub_shared_call:
+ BFD_ASSERT (hstub->htarget->toc_section != NULL);
+ /* The first instruction in the glink code needs to be
+ cooked to hold the correct offset in the toc. It will
+ be filled by xcoff_stub_create_relocations. */
+ for (i = 0; i < bfd_xcoff_stub_shared_call_size(output_bfd) / 4; i++)
+ bfd_put_32 (stub_bfd,
+ (bfd_vma) bfd_xcoff_stub_shared_call_code(output_bfd, i),
+ &p[4 * i]);
+
+ break;
+
+ default:
+ BFD_FAIL ();
+ return false;
+ }
+ return true;
+}
+
+/* Check relocations and adds stubs if needed. */
+
+bool
+bfd_xcoff_size_stubs (struct bfd_link_info *info)
+{
+ struct xcoff_link_hash_table *htab = xcoff_hash_table (info);
+ struct xcoff_loader_info *ldinfo = &(htab->ldinfo);
+
+ while (1)
+ {
+ bfd *input_bfd;
+ bool stub_changed = false;
+
+ for (input_bfd = info->input_bfds;
+ input_bfd != NULL;
+ input_bfd = input_bfd->link.next)
+ {
+ asection *section;
+ bfd_size_type symcount;
+ bfd_size_type symesz;
+ bfd_byte *esyms;
+
+ if (bfd_get_flavour (input_bfd) != bfd_target_xcoff_flavour)
+ continue;
+
+ symcount = obj_raw_syment_count (input_bfd);
+ if (!symcount)
+ continue;
+ symesz = bfd_coff_symesz (input_bfd);
+ esyms = (bfd_byte *) obj_coff_external_syms (input_bfd);
+
+ /* Walk over each section attached to the input bfd. */
+ for (section = input_bfd->sections;
+ section != NULL;
+ section = section->next)
+ {
+ struct internal_reloc *internal_relocs;
+ struct internal_reloc *irel, *irelend;
+
+ /* If there aren't any relocs, then there's nothing more
+ to do. */
+ if ((section->flags & SEC_RELOC) == 0
+ || section->reloc_count == 0)
+ continue;
+
+ /* If this section is a link-once section that will be
+ discarded, then don't create any stubs. */
+ if (section->output_section == NULL
+ || section->output_section->owner != info->output_bfd)
+ continue;
+
+ /* This section have been garbage-collected. */
+ if (section->gc_mark == 0)
+ continue;
+
+ /* Read in the relocs. */
+ internal_relocs = (xcoff_read_internal_relocs
+ (input_bfd, section, true, NULL,
+ false, NULL));
+ if (internal_relocs == NULL)
+ goto error_ret;
+
+ irel = internal_relocs;
+ irelend = irel + section->reloc_count;
+ for (; irel < irelend; irel++)
+ {
+ enum xcoff_stub_type stub_type;
+ struct xcoff_link_hash_entry *hsym = NULL;
+ struct xcoff_link_hash_entry *hstub_csect = NULL;
+ struct xcoff_stub_hash_entry *hstub = NULL;
+ asection *sym_sec;
+ bfd_vma sym_value;
+ bfd_vma destination;
+ char *stub_name;
+
+ if (irel->r_symndx == -1)
+ continue;
+
+ switch (irel->r_type)
+ {
+ default:
+ continue;
+
+ case R_BR:
+ case R_RBR:
+ break;
+ }
+
+ /* Retrieve targeted symbol address */
+ hsym = obj_xcoff_sym_hashes (input_bfd)[irel->r_symndx];
+ if (hsym == NULL)
+ {
+ struct internal_syment sym;
+ if ((long unsigned int)irel->r_symndx > symcount)
+ {
+ BFD_FAIL();
+ goto error_ret;
+ }
+
+ bfd_coff_swap_sym_in (input_bfd,
+ (void *) esyms + irel->r_symndx * symesz,
+ (void *) &sym);
+
+ sym_sec = xcoff_data (input_bfd)->csects[irel->r_symndx];
+ sym_value = sym.n_value - sym_sec->vma;
+
+ destination = (sym_value
+ + sym_sec->output_section->vma
+ + sym_sec->output_offset);
+ }
+ else if (hsym->root.type == bfd_link_hash_defined
+ || hsym->root.type == bfd_link_hash_defweak)
+ {
+ sym_sec = hsym->root.u.def.section;
+ sym_value = hsym->root.u.def.value;
+ destination = (sym_value
+ + sym_sec->output_section->vma
+ + sym_sec->output_offset);
+ }
+ else
+ {
+ bfd_set_error (bfd_error_bad_value);
+ goto error_ret;
+ }
+
+ /* I'm not sure how to handle this case. Skip it for now. */
+ if (bfd_is_abs_section (sym_sec))
+ continue;
+
+ stub_type = bfd_xcoff_type_of_stub (section, irel, destination, hsym);
+
+ if (stub_type == xcoff_stub_none)
+ continue;
+
+ /* Get a stub csect in ranch. */
+ hstub_csect = xcoff_stub_get_csect_in_range (section, info, true);
+ if (!hstub_csect)
+ {
+ /* xgettext:c-format */
+ _bfd_error_handler (_("%pB: Unable to find a stub csect in range"
+ "of relocation at %#" PRIx64 " targeting"
+ "'%s'"),
+ section->owner, (uint64_t) irel->r_vaddr,
+ hsym->root.root.string);
+ goto error_ret;
+ }
+
+ /* Get the name of this stub. */
+ stub_name = xcoff_stub_name (hsym, hstub_csect);
+ if (!stub_name)
+ goto error_ret;
+
+ hstub = xcoff_stub_hash_lookup (&(xcoff_hash_table (info)->stub_hash_table),
+ stub_name, false, false);
+
+ /* A stub entry inside the in range csect already exists. */
+ if (hstub != NULL)
+ {
+ free (stub_name);
+ continue;
+ }
+
+ stub_changed = true;
+
+ hstub = xcoff_add_stub (stub_name, hstub_csect, hsym, info, stub_type);
+ if (hstub == NULL)
+ {
+ /* xgettext:c-format */
+ _bfd_error_handler (_("%pB: Cannot create stub entry '%s'"),
+ section->owner, stub_name);
+ free (stub_name);
+ goto error_ret;
+ }
+
+ hstub->stub_type = stub_type;
+ hstub->hcsect = hstub_csect;
+ hstub->target_section = sym_sec;
+ free (stub_name);
+ }
+ }
+ }
+
+ if (!stub_changed)
+ break;
+
+ /* Update the size of the loader. */
+ if (xcoff_hash_table (info)->loader_section
+ && !xcoff_size_loader_section (ldinfo))
+ goto error_ret;
+
+ /* Ask the linker to do its stuff. */
+ (*htab->params->layout_sections_again) ();
+
+ }
+ return true;
+
+ error_ret:
+ bfd_set_error (bfd_error_bad_value);
+ return false;
+}
+
+bool
+bfd_xcoff_build_stubs (struct bfd_link_info *info)
+{
+ struct xcoff_link_hash_table *htab = xcoff_hash_table (info);
+ asection *stub_sec;
+
+ for (stub_sec = htab->params->stub_bfd->sections;
+ stub_sec != NULL;
+ stub_sec = stub_sec->next)
+ {
+ bfd_size_type size;
+
+ /* Allocate memory to hold the linker stubs. */
+ size = stub_sec->size;
+ stub_sec->contents = bfd_zalloc (htab->params->stub_bfd, size);
+ if (stub_sec->contents == NULL && size != 0)
+ return false;
+
+ }
+
+ /* Build the stubs as directed by the stub hash table. */
+ bfd_hash_traverse (&htab->stub_hash_table, xcoff_build_one_stub, info);
+ return true;
+}
+
+/* Create and apply relocations made by a stub entry. */
+static bool
+xcoff_stub_create_relocations (struct bfd_hash_entry *bh, void * inf)
+{
+ struct xcoff_stub_hash_entry *hstub
+ = (struct xcoff_stub_hash_entry *) bh;
+ struct xcoff_final_link_info *flinfo
+ = (struct xcoff_final_link_info *) inf;
+
+ bfd *output_bfd;
+ struct internal_reloc *irel;
+ struct xcoff_link_hash_entry **rel_hash;
+ struct xcoff_link_hash_entry *htarget;
+ asection *sec, *osec;
+ bfd_vma off;
+ bfd_byte *p;
+
+ htarget = hstub->htarget;
+ sec = hstub->hcsect->root.u.def.section;
+ osec = sec->output_section;
+
+ irel = (flinfo->section_info[osec->target_index].relocs
+ + osec->reloc_count);
+ rel_hash = (flinfo->section_info[osec->target_index].rel_hashes
+ + osec->output_section->reloc_count);
+ *rel_hash = NULL;
+ output_bfd = flinfo->output_bfd;
+
+ irel->r_symndx = htarget->indx;
+ irel->r_vaddr = (osec->vma
+ + sec->output_offset
+ + hstub->hcsect->root.u.def.value
+ + hstub->stub_offset);
+
+ p = (sec->contents
+ + hstub->stub_offset);
+
+ switch (hstub->stub_type)
+ {
+ default:
+ BFD_FAIL ();
+ return false;
+
+ /* The first instruction of this stub code need
+ a R_TOC relocation. */
+ case xcoff_stub_indirect_call:
+ case xcoff_stub_shared_call:
+ irel->r_size = 0xf;
+ irel->r_type = R_TOC;
+
+ /* Retrieve the toc offset of the target which is
+ a function descriptor. */
+ BFD_ASSERT (htarget->toc_section != NULL);
+ if ((htarget->flags & XCOFF_SET_TOC) != 0)
+ off = hstub->htarget->u.toc_offset;
+ else
+ off = (htarget->toc_section->output_section->vma
+ + htarget->toc_section->output_offset
+ - xcoff_data (flinfo->output_bfd)->toc);
+ if ((off & 0xffff) != off)
+ {
+ _bfd_error_handler
+ (_("TOC overflow during stub generation; try -mminimal-toc "
+ "when compiling"));
+ bfd_set_error (bfd_error_file_too_big);
+ return false;
+ }
+
+ bfd_put_16 (output_bfd, off & 0xffff, p+2);
+ break;
+ }
+
+ ++osec->reloc_count;
+ return true;
+}
+
+