/* POWER/PowerPC XCOFF linker support.
- Copyright (C) 1995-2022 Free Software Foundation, Inc.
+ Copyright (C) 1995-2023 Free Software Foundation, Inc.
Written by Ian Lance Taylor <ian@cygnus.com>, Cygnus Support.
This file is part of BFD, the Binary File Descriptor library.
{
struct bfd_link_hash_table root;
+ /* The stub hash table. */
+ struct bfd_hash_table stub_hash_table;
+
+ /* Info passed by the linker. */
+ struct bfd_xcoff_link_params *params;
+
/* The .debug string hash table. We need to compute this while
reading the input files, so that we know how large the .debug
section will be before we assign section positions. */
bfd_byte *external_relocs;
};
+#define xcoff_stub_hash_entry(ent) \
+ ((struct xcoff_stub_hash_entry *)(ent))
+
+#define xcoff_stub_hash_lookup(table, string, create, copy) \
+ ((struct xcoff_stub_hash_entry *) \
+ bfd_hash_lookup ((table), (string), (create), (copy)))
+
static bool xcoff_mark (struct bfd_link_info *, asection *);
\f
/* Read the contents of a section. */
-static bool
+static bfd_byte *
xcoff_get_section_contents (bfd *abfd, asection *sec)
{
if (coff_section_data (abfd, sec) == NULL)
sec->used_by_bfd = bfd_zalloc (abfd, amt);
if (sec->used_by_bfd == NULL)
- return false;
+ return NULL;
}
- if (coff_section_data (abfd, sec)->contents == NULL)
+ bfd_byte *contents = coff_section_data (abfd, sec)->contents;
+ if (contents == NULL)
{
- bfd_byte *contents;
-
- if (! bfd_malloc_and_get_section (abfd, sec, &contents))
+ if (bfd_malloc_and_get_section (abfd, sec, &contents))
+ coff_section_data (abfd, sec)->contents = contents;
+ else
{
free (contents);
- return false;
+ contents = NULL;
}
- coff_section_data (abfd, sec)->contents = contents;
}
- return true;
+ return contents;
}
/* Get the size required to hold the dynamic symbols. */
}
lsec = bfd_get_section_by_name (abfd, ".loader");
- if (lsec == NULL)
+ if (lsec == NULL || (lsec->flags & SEC_HAS_CONTENTS) == 0)
{
bfd_set_error (bfd_error_no_symbols);
return -1;
}
- if (! xcoff_get_section_contents (abfd, lsec))
+ contents = xcoff_get_section_contents (abfd, lsec);
+ if (!contents)
return -1;
- contents = coff_section_data (abfd, lsec)->contents;
bfd_xcoff_swap_ldhdr_in (abfd, (void *) contents, &ldhdr);
}
lsec = bfd_get_section_by_name (abfd, ".loader");
- if (lsec == NULL)
+ if (lsec == NULL || (lsec->flags & SEC_HAS_CONTENTS) == 0)
{
bfd_set_error (bfd_error_no_symbols);
return -1;
}
- if (! xcoff_get_section_contents (abfd, lsec))
+ contents = xcoff_get_section_contents (abfd, lsec);
+ if (!contents)
return -1;
- contents = coff_section_data (abfd, lsec)->contents;
-
- coff_section_data (abfd, lsec)->keep_contents = true;
bfd_xcoff_swap_ldhdr_in (abfd, contents, &ldhdr);
}
lsec = bfd_get_section_by_name (abfd, ".loader");
- if (lsec == NULL)
+ if (lsec == NULL || (lsec->flags & SEC_HAS_CONTENTS) == 0)
{
bfd_set_error (bfd_error_no_symbols);
return -1;
}
- if (! xcoff_get_section_contents (abfd, lsec))
+ contents = xcoff_get_section_contents (abfd, lsec);
+ if (!contents)
return -1;
- contents = coff_section_data (abfd, lsec)->contents;
bfd_xcoff_swap_ldhdr_in (abfd, (struct external_ldhdr *) contents, &ldhdr);
}
lsec = bfd_get_section_by_name (abfd, ".loader");
- if (lsec == NULL)
+ if (lsec == NULL || (lsec->flags & SEC_HAS_CONTENTS) == 0)
{
bfd_set_error (bfd_error_no_symbols);
return -1;
}
- if (! xcoff_get_section_contents (abfd, lsec))
+ contents = xcoff_get_section_contents (abfd, lsec);
+ if (!contents)
return -1;
- contents = coff_section_data (abfd, lsec)->contents;
bfd_xcoff_swap_ldhdr_in (abfd, contents, &ldhdr);
return entryp;
}
\f
+
+/* Initialize an entry in the stub hash table. */
+static struct bfd_hash_entry *
+stub_hash_newfunc (struct bfd_hash_entry *entry,
+ struct bfd_hash_table *table,
+ const char *string)
+{
+ /* Allocate the structure if it has not already been allocated by a
+ subclass. */
+ if (entry == NULL)
+ {
+ entry = bfd_hash_allocate (table,
+ sizeof (struct xcoff_stub_hash_entry));
+ if (entry == NULL)
+ return entry;
+ }
+
+ /* Call the allocation method of the superclass. */
+ entry = bfd_hash_newfunc (entry, table, string);
+ if (entry != NULL)
+ {
+ struct xcoff_stub_hash_entry *hsh;
+
+ /* Initialize the local fields. */
+ hsh = (struct xcoff_stub_hash_entry *) entry;
+ hsh->stub_type = xcoff_stub_none;
+ hsh->hcsect = NULL;
+ hsh->stub_offset = 0;
+ hsh->target_section = NULL;
+ hsh->htarget = NULL;
+ }
+
+ return entry;
+}
+
/* Routine to create an entry in an XCOFF link hash table. */
static struct bfd_hash_entry *
htab_delete (ret->archive_info);
if (ret->debug_strtab)
_bfd_stringtab_free (ret->debug_strtab);
+
+ bfd_hash_table_free (&ret->stub_hash_table);
_bfd_generic_link_hash_table_free (obfd);
}
return NULL;
}
+ /* Init the stub hash table too. */
+ if (!bfd_hash_table_init (&ret->stub_hash_table, stub_hash_newfunc,
+ sizeof (struct xcoff_stub_hash_entry)))
+ {
+ _bfd_xcoff_bfd_link_hash_table_free (abfd);
+ return NULL;
+ }
+
isxcoff64 = bfd_coff_debug_string_prefix_length (abfd) == 4;
ret->debug_strtab = _bfd_xcoff_stringtab_init (isxcoff64);
o_snloader field in the a.out header, rather than grabbing the
section by name. */
lsec = bfd_get_section_by_name (abfd, ".loader");
- if (lsec == NULL)
+ if (lsec == NULL || (lsec->flags & SEC_HAS_CONTENTS) == 0)
{
_bfd_error_handler
(_("%pB: dynamic object with no .loader section"),
return false;
}
- if (! xcoff_get_section_contents (abfd, lsec))
+ contents = xcoff_get_section_contents (abfd, lsec);
+ if (!contents)
return false;
- contents = coff_section_data (abfd, lsec)->contents;
/* Remove the sections from this object, so that they do not get
included in the link. */
}
}
- if (contents != NULL && ! coff_section_data (abfd, lsec)->keep_contents)
- {
- free (coff_section_data (abfd, lsec)->contents);
- coff_section_data (abfd, lsec)->contents = NULL;
- }
+ free (contents);
+ coff_section_data (abfd, lsec)->contents = NULL;
/* Record this file in the import files. */
n = bfd_alloc (abfd, (bfd_size_type) sizeof (struct xcoff_import_file));
/* This is an external reference. */
if (sym.n_sclass == C_HIDEXT
|| sym.n_scnum != N_UNDEF
- || aux.x_csect.x_scnlen.l != 0)
+ || aux.x_csect.x_scnlen.u64 != 0)
{
_bfd_error_handler
/* xgettext:c-format */
(_("%pB: bad XTY_ER symbol `%s': class %d scnum %d "
"scnlen %" PRId64),
abfd, name, sym.n_sclass, sym.n_scnum,
- (int64_t) aux.x_csect.x_scnlen.l);
+ aux.x_csect.x_scnlen.u64);
bfd_set_error (bfd_error_bad_value);
goto error_return;
}
if (aux.x_csect.x_smclas == XMC_TC0)
{
if (sym.n_sclass != C_HIDEXT
- || aux.x_csect.x_scnlen.l != 0)
+ || aux.x_csect.x_scnlen.u64 != 0)
{
_bfd_error_handler
/* xgettext:c-format */
- (_("%pB: XMC_TC0 symbol `%s' is class %d scnlen %" PRId64),
- abfd, name, sym.n_sclass, (int64_t) aux.x_csect.x_scnlen.l);
+ (_("%pB: XMC_TC0 symbol `%s' is class %d scnlen %" PRIu64),
+ abfd, name, sym.n_sclass, aux.x_csect.x_scnlen.u64);
bfd_set_error (bfd_error_bad_value);
goto error_return;
}
&& sym.n_sclass == C_HIDEXT
&& info->output_bfd->xvec == abfd->xvec
&& ((bfd_xcoff_is_xcoff32 (abfd)
- && aux.x_csect.x_scnlen.l == 4)
+ && aux.x_csect.x_scnlen.u64 == 4)
|| (bfd_xcoff_is_xcoff64 (abfd)
- && aux.x_csect.x_scnlen.l == 8)))
+ && aux.x_csect.x_scnlen.u64 == 8)))
{
asection *enclosing;
struct internal_reloc *relocs;
if (! bfd_is_abs_section (enclosing)
&& ((bfd_vma) sym.n_value < enclosing->vma
- || ((bfd_vma) sym.n_value + aux.x_csect.x_scnlen.l
+ || (sym.n_value + aux.x_csect.x_scnlen.u64
> enclosing->vma + enclosing->size)))
{
_bfd_error_handler
csect->filepos = (enclosing->filepos
+ sym.n_value
- enclosing->vma);
- csect->size = aux.x_csect.x_scnlen.l;
+ csect->size = aux.x_csect.x_scnlen.u64;
+ csect->rawsize = aux.x_csect.x_scnlen.u64;
csect->flags |= SEC_ALLOC | SEC_LOAD | SEC_HAS_CONTENTS;
csect->alignment_power = SMTYP_ALIGN (aux.x_csect.x_smtyp);
bool bad;
bad = false;
- if (aux.x_csect.x_scnlen.l < 0
- || (aux.x_csect.x_scnlen.l
- >= esym - (bfd_byte *) obj_coff_external_syms (abfd)))
+ if (aux.x_csect.x_scnlen.u64
+ >= (size_t) (esym - (bfd_byte *) obj_coff_external_syms (abfd)))
bad = true;
if (! bad)
{
- section = xcoff_data (abfd)->csects[aux.x_csect.x_scnlen.l];
+ section = xcoff_data (abfd)->csects[aux.x_csect.x_scnlen.u64];
if (section == NULL
|| (section->flags & SEC_HAS_CONTENTS) == 0)
bad = true;
if (csect == NULL)
goto error_return;
csect->vma = sym.n_value;
- csect->size = aux.x_csect.x_scnlen.l;
+ csect->size = aux.x_csect.x_scnlen.u64;
csect->alignment_power = SMTYP_ALIGN (aux.x_csect.x_smtyp);
/* There are a number of other fields and section flags
which we do not bother to set. */
csect->flags |= SEC_IS_COMMON;
csect->size = 0;
section = csect;
- value = aux.x_csect.x_scnlen.l;
+ value = aux.x_csect.x_scnlen.u64;
}
break;
/* If we are not keeping memory, free the reloc information. */
if (! info->keep_memory
- && coff_section_data (abfd, o) != NULL
- && ! coff_section_data (abfd, o)->keep_relocs)
+ && coff_section_data (abfd, o) != NULL)
{
free (coff_section_data (abfd, o)->relocs);
coff_section_data (abfd, o)->relocs = NULL;
*pneeded = false;
lsec = bfd_get_section_by_name (abfd, ".loader");
- if (lsec == NULL)
+ if (lsec == NULL || (lsec->flags & SEC_HAS_CONTENTS) == 0)
/* There are no symbols, so don't try to include it. */
return true;
- if (! xcoff_get_section_contents (abfd, lsec))
+ contents = xcoff_get_section_contents (abfd, lsec);
+ if (!contents)
return false;
- contents = coff_section_data (abfd, lsec)->contents;
bfd_xcoff_swap_ldhdr_in (abfd, contents, &ldhdr);
}
}
- /* We do not need this shared object. */
- if (contents != NULL && ! coff_section_data (abfd, lsec)->keep_contents)
- {
- free (coff_section_data (abfd, lsec)->contents);
- coff_section_data (abfd, lsec)->contents = NULL;
- }
+ /* We do not need this shared object's .loader section. */
+ free (contents);
+ coff_section_data (abfd, lsec)->contents = NULL;
return true;
}
}
if (! info->keep_memory
- && coff_section_data (sec->owner, sec) != NULL
- && ! coff_section_data (sec->owner, sec)->keep_relocs)
+ && coff_section_data (sec->owner, sec) != NULL)
{
free (coff_section_data (sec->owner, sec)->relocs);
coff_section_data (sec->owner, sec)->relocs = NULL;
}
}
+/* Initialize the back-end with linker infos. */
+
+bool
+bfd_xcoff_link_init (struct bfd_link_info *info,
+ struct bfd_xcoff_link_params *params)
+{
+ xcoff_hash_table (info)->params = params;
+
+ return true;
+}
+
/* Record the number of elements in a set. This is used to output the
correct csect length. */
{
/* Grab the contents of SUB's .debug section, if any. */
subdeb = bfd_get_section_by_name (sub, ".debug");
- if (subdeb != NULL && subdeb->size > 0)
+ if (subdeb != NULL
+ && subdeb->size != 0
+ && (subdeb->flags & SEC_HAS_CONTENTS) != 0)
{
/* We use malloc and copy the names into the debug
stringtab, rather than bfd_alloc, because I expect
return true;
}
\f
+
+/* 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;
+}
+
+
/* Return the section that defines H. Return null if no section does. */
static asection *
{
unsigned long indx;
- indx = aux.x_csect.x_scnlen.l;
+ indx = aux.x_csect.x_scnlen.u64;
if (indx < obj_raw_syment_count (input_bfd))
{
long symindx;
symindx = flinfo->sym_indices[indx];
if (symindx < 0)
{
- aux.x_csect.x_scnlen.l = 0;
+ aux.x_csect.x_scnlen.u64 = 0;
}
else
{
- aux.x_csect.x_scnlen.l = symindx;
+ aux.x_csect.x_scnlen.u64 = symindx;
}
}
}
|| isymp->n_sclass == C_BLOCK
|| isymp->n_sclass == C_FCN)
{
- indx = aux.x_sym.x_fcnary.x_fcn.x_endndx.l;
+ indx = aux.x_sym.x_fcnary.x_fcn.x_endndx.u32;
if (indx > 0
&& indx < obj_raw_syment_count (input_bfd))
{
indx = output_index;
else
indx = flinfo->sym_indices[indx];
- aux.x_sym.x_fcnary.x_fcn.x_endndx.l = indx;
+ aux.x_sym.x_fcnary.x_fcn.x_endndx.u32 = indx;
}
}
- indx = aux.x_sym.x_tagndx.l;
+ indx = aux.x_sym.x_tagndx.u32;
if (indx > 0 && indx < obj_raw_syment_count (input_bfd))
{
long symindx;
symindx = flinfo->sym_indices[indx];
if (symindx < 0)
- aux.x_sym.x_tagndx.l = 0;
+ aux.x_sym.x_tagndx.u32 = 0;
else
- aux.x_sym.x_tagndx.l = symindx;
+ aux.x_sym.x_tagndx.u32 = symindx;
}
}
/* Adjust the reloc address and symbol index. */
- irel->r_vaddr += offset;
-
r_symndx = irel->r_symndx;
if (r_symndx == -1)
else
h = obj_xcoff_sym_hashes (input_bfd)[r_symndx];
+ /* In case of a R_BR or R_RBR, change the target if
+ a stub is being called. */
+ if (h != NULL
+ && (irel->r_type == R_BR
+ || irel->r_type == R_RBR))
+ {
+ asection *sym_sec;
+ bfd_vma dest;
+ struct xcoff_stub_hash_entry *hstub = NULL;
+ enum xcoff_stub_type stub_type;
+
+ if (h->root.type == bfd_link_hash_defined
+ || h->root.type == bfd_link_hash_defweak)
+ {
+ sym_sec = h->root.u.def.section;
+ dest = (h->root.u.def.value
+ + sym_sec->output_section->vma
+ + sym_sec->output_offset);
+ }
+ else
+ {
+ BFD_FAIL ();
+ goto err_out;
+ }
+
+ stub_type = bfd_xcoff_type_of_stub (o, irel, dest, h);
+ if (stub_type != xcoff_stub_none)
+ {
+ hstub = bfd_xcoff_get_stub_entry (o, h, flinfo->info);
+ if (hstub == NULL)
+ goto err_out;
+
+ h = hstub->hcsect;
+ }
+
+ }
+
+ irel->r_vaddr += offset;
+
if (r_symndx != -1 && flinfo->info->strip != strip_all)
{
+
if (h != NULL
&& h->smclas != XMC_TD
&& (irel->r_type == R_TOC
memset (&iraux, 0, sizeof iraux);
iraux.x_csect.x_smtyp = XTY_SD;
iraux.x_csect.x_smclas = XMC_TC0;
- iraux.x_csect.x_scnlen.l = 0;
+ iraux.x_csect.x_scnlen.u64 = 0;
bfd_coff_swap_aux_out (output_bfd, &iraux, T_NULL, C_HIDEXT, 0, 1,
flinfo->outsyms + bfd_coff_symesz (output_bfd));
irel->r_symndx = obj_raw_syment_count (output_bfd);
}
- BFD_ASSERT (h->ldindx >= 0);
-
/* Initialize the aux union here instead of closer to when it is
written out below because the length of the csect depends on
whether the output is 32 or 64 bit. */
memset (&iraux, 0, sizeof iraux);
iraux.x_csect.x_smtyp = XTY_SD;
- /* iraux.x_csect.x_scnlen.l = 4 or 8, see below. */
+ /* iraux.x_csect.x_scnlen.u64 = 4 or 8, see below. */
iraux.x_csect.x_smclas = XMC_TC;
/* 32 bit uses a 32 bit R_POS to do the relocations
if (bfd_xcoff_is_xcoff64 (output_bfd))
{
irel->r_size = 63;
- iraux.x_csect.x_scnlen.l = 8;
+ iraux.x_csect.x_scnlen.u64 = 8;
}
else if (bfd_xcoff_is_xcoff32 (output_bfd))
{
irel->r_size = 31;
- iraux.x_csect.x_scnlen.l = 4;
+ iraux.x_csect.x_scnlen.u64 = 4;
}
else
return false;
flinfo->section_info[oindx].rel_hashes[osec->reloc_count] = NULL;
++osec->reloc_count;
- if (!xcoff_create_ldrel (output_bfd, flinfo, osec,
- output_bfd, irel, NULL, h))
- return false;
+ /* There are two kind of linker-created TOC entry.
+ The ones importing their symbols from outside, made for the
+ global linkage. These symbols have XCOFF_LDREL set and only
+ requires a loader relocation on their imported symbol.
+ On the other hand, symbols without XCOFF_LDREL are TOC entries
+ of internal symbols (like function descriptors made for stubs).
+ These symbols needs a loader relocation over .data and this
+ relocation must be applied. */
+
+ if ((h->flags & XCOFF_LDREL) != 0
+ && h->ldindx >= 0)
+ {
+ if (!xcoff_create_ldrel (output_bfd, flinfo, osec,
+ output_bfd, irel, NULL, h))
+ return false;
+ }
+ else
+ {
+ bfd_byte *p;
+ bfd_vma val;
+
+ p = tocsec->contents + h->u.toc_offset;
+ val = (h->root.u.def.value
+ + h->root.u.def.section->output_section->vma
+ + h->root.u.def.section->output_offset);
+
+ if (bfd_xcoff_is_xcoff64 (output_bfd))
+ bfd_put_64 (output_bfd, val, p);
+ else if (bfd_xcoff_is_xcoff32 (output_bfd))
+ bfd_put_32 (output_bfd, val, p);
+ else
+ return false;
+
+ if (!xcoff_create_ldrel (output_bfd, flinfo, osec,
+ output_bfd, irel, h->root.u.def.section, h))
+ return false;
+ }
/* We need to emit a symbol to define a csect which holds
the reloc. */
isym.n_sclass = C_HIDEXT;
aux.x_csect.x_smtyp = XTY_SD;
- if ((h->flags & XCOFF_HAS_SIZE) != 0)
+ /* For stub symbols, the section already has its correct size. */
+ if (h->root.u.def.section->owner == xcoff_hash_table (flinfo->info)->params->stub_bfd)
+ {
+ aux.x_csect.x_scnlen.u64 = h->root.u.def.section->size;
+ }
+ else if ((h->flags & XCOFF_HAS_SIZE) != 0)
{
for (l = xcoff_hash_table (flinfo->info)->size_list;
l != NULL;
{
if (l->h == h)
{
- aux.x_csect.x_scnlen.l = l->size;
+ aux.x_csect.x_scnlen.u64 = l->size;
break;
}
}
isym.n_scnum = h->root.u.c.p->section->output_section->target_index;
isym.n_sclass = C_EXT;
aux.x_csect.x_smtyp = XTY_CM;
- aux.x_csect.x_scnlen.l = h->root.u.c.size;
+ aux.x_csect.x_scnlen.u64 = h->root.u.c.size;
}
else
abort ();
outsym += bfd_coff_symesz (output_bfd);
aux.x_csect.x_smtyp = XTY_LD;
- aux.x_csect.x_scnlen.l = obj_raw_syment_count (output_bfd);
+ aux.x_csect.x_scnlen.u64 = obj_raw_syment_count (output_bfd);
bfd_coff_swap_aux_out (output_bfd, (void *) &aux, T_NULL, C_EXT, 0, 1,
(void *) outsym);
outsym += bfd_coff_auxesz (output_bfd);
sub = p->u.indirect.section->owner;
if (! sub->output_has_begun)
{
- if (! xcoff_link_input_bfd (&flinfo, sub))
- goto error_return;
+ if (sub == xcoff_hash_table (info)->params->stub_bfd)
+ {
+ continue;
+ }
+ else
+ {
+ if (! xcoff_link_input_bfd (&flinfo, sub))
+ {
+ _bfd_error_handler
+ (_("Unable to link input file: %s"), sub->filename);
+ bfd_set_error (bfd_error_sorry);
+ goto error_return;
+ }
+ }
sub->output_has_begun = true;
}
}
input files. */
bfd_hash_traverse (&info->hash->table, xcoff_write_global_symbol, &flinfo);
+ /* Write out the relocations created by stub entries. The symbols
+ will have been already written by xcoff_write_global_symbol. */
+ bfd_hash_traverse (&xcoff_hash_table(info)->stub_hash_table,
+ xcoff_stub_create_relocations,
+ &flinfo);
+
free (flinfo.outsyms);
flinfo.outsyms = NULL;
flinfo.section_info = NULL;
}
+ /* Write out the stub sections. */
+ for (o = xcoff_hash_table (info)->params->stub_bfd->sections;
+ o != NULL; o = o->next)
+ {
+ if ((o->flags & SEC_HAS_CONTENTS) == 0
+ || o->size == 0)
+ continue;
+
+ if (!bfd_set_section_contents (abfd, o->output_section, o->contents,
+ (file_ptr) o->output_offset, o->size))
+ goto error_return;
+ }
+
/* Write out the loader section contents. */
o = xcoff_hash_table (info)->loader_section;
if (o != NULL