eb8476a6e2
* elflink.c (bfd_elf_record_link_assignment): Remove --defsym symbols special case. ld/ * ldexp.h (etree_union): Add defsym member to the assign member structure. (exp_assign): Add hidden argument to prototype. * ldexp.c (exp_fold_tree_1): Use the defsym member to handle --defsym symbols. (exp_assop): Add defsym argument, initialize the defsym member of the assign structure. (exp_assign): Handle hidden symbols. (exp_defsym): Update to use the defsym argument to exp_assop. (exp_provide): Update to handle the defsym argument to exp_assop. * ldlex.l (HIDDEN): New token. * ldgram.y (HIDDEN): Likewise. (assignment, section): Update calls to exp_assign. * ldctor.c (ldctor_build_sets): Likewise. * mri.c (mri_format): Likewise. * ldlang.c (lang_insert_orphan, lang_leave_overlay): Likewise. (open_input_bfds): Remove --defsym symbols special case. * emultempl/beos.em (gld_${EMULATION_NAME}_set_symbols): Update call to exp_assign. * emultempl/pe.em (gld_${EMULATION_NAME}_set_symbols): Likewise. * emultempl/pep.em (gld_${EMULATION_NAME}_set_symbols): Likewise. * emultempl/spuelf.em (spu_place_special_section): Likewise. * emultempl/xtensaelf.em (ld_xtensa_insert_page_offsets): Likewise. * ld.texinfo (Assigning Values to Symbols): Add HIDDEN. (HIDDEN): New subsection.
1963 lines
51 KiB
Plaintext
1963 lines
51 KiB
Plaintext
# This shell script emits a C file. -*- C -*-
|
|
# Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
|
|
# Free Software Foundation, Inc.
|
|
#
|
|
# This file is part of the GNU Binutils.
|
|
#
|
|
# 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, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
|
|
# MA 02110-1301, USA.
|
|
#
|
|
|
|
# This file is sourced from elf32.em, and defines extra xtensa-elf
|
|
# specific routines.
|
|
#
|
|
fragment <<EOF
|
|
|
|
#include <xtensa-config.h>
|
|
#include "../bfd/elf-bfd.h"
|
|
#include "../bfd/libbfd.h"
|
|
#include "elf/xtensa.h"
|
|
#include "bfd.h"
|
|
|
|
/* Provide default values for new configuration settings. */
|
|
#ifndef XSHAL_ABI
|
|
#define XSHAL_ABI 0
|
|
#endif
|
|
|
|
static void xtensa_wild_group_interleave (lang_statement_union_type *);
|
|
static void xtensa_colocate_output_literals (lang_statement_union_type *);
|
|
static void xtensa_strip_inconsistent_linkonce_sections
|
|
(lang_statement_list_type *);
|
|
|
|
|
|
/* This number is irrelevant until we turn on use_literal_pages */
|
|
static bfd_vma xtensa_page_power = 12; /* 4K pages. */
|
|
|
|
/* To force a page break between literals and text, change
|
|
xtensa_use_literal_pages to "TRUE". */
|
|
static bfd_boolean xtensa_use_literal_pages = FALSE;
|
|
|
|
#define EXTRA_VALIDATION 0
|
|
|
|
|
|
static char *
|
|
elf_xtensa_choose_target (int argc ATTRIBUTE_UNUSED,
|
|
char **argv ATTRIBUTE_UNUSED)
|
|
{
|
|
if (XCHAL_HAVE_BE)
|
|
return "${BIG_OUTPUT_FORMAT}";
|
|
else
|
|
return "${LITTLE_OUTPUT_FORMAT}";
|
|
}
|
|
|
|
|
|
static void
|
|
elf_xtensa_before_parse (void)
|
|
{
|
|
/* Just call the default hook.... Tensilica's version of this function
|
|
does some other work that isn't relevant here. */
|
|
gld${EMULATION_NAME}_before_parse ();
|
|
}
|
|
|
|
|
|
static void
|
|
remove_section (bfd *abfd, asection *os)
|
|
{
|
|
asection **spp;
|
|
for (spp = &abfd->sections; *spp; spp = &(*spp)->next)
|
|
if (*spp == os)
|
|
{
|
|
*spp = os->next;
|
|
os->owner->section_count--;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static bfd_boolean
|
|
replace_insn_sec_with_prop_sec (bfd *abfd,
|
|
const char *insn_sec_name,
|
|
const char *prop_sec_name,
|
|
char **error_message)
|
|
{
|
|
asection *insn_sec;
|
|
asection *prop_sec;
|
|
bfd_byte *prop_contents = NULL;
|
|
bfd_byte *insn_contents = NULL;
|
|
unsigned entry_count;
|
|
unsigned entry;
|
|
Elf_Internal_Shdr *rel_hdr;
|
|
Elf_Internal_Rela *internal_relocs = NULL;
|
|
unsigned reloc_count;
|
|
|
|
*error_message = "";
|
|
insn_sec = bfd_get_section_by_name (abfd, insn_sec_name);
|
|
if (insn_sec == NULL)
|
|
return TRUE;
|
|
entry_count = insn_sec->size / 8;
|
|
|
|
prop_sec = bfd_get_section_by_name (abfd, prop_sec_name);
|
|
if (prop_sec != NULL && insn_sec != NULL)
|
|
{
|
|
*error_message = _("file already has property tables");
|
|
return FALSE;
|
|
}
|
|
|
|
if (insn_sec->size != 0)
|
|
{
|
|
insn_contents = (bfd_byte *) bfd_malloc (insn_sec->size);
|
|
if (insn_contents == NULL)
|
|
{
|
|
*error_message = _("out of memory");
|
|
goto cleanup;
|
|
}
|
|
if (! bfd_get_section_contents (abfd, insn_sec, insn_contents,
|
|
(file_ptr) 0, insn_sec->size))
|
|
{
|
|
*error_message = _("failed to read section contents");
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/* Create a property table section for it. */
|
|
prop_sec_name = strdup (prop_sec_name);
|
|
prop_sec = bfd_make_section_with_flags
|
|
(abfd, prop_sec_name, bfd_get_section_flags (abfd, insn_sec));
|
|
if (prop_sec == NULL
|
|
|| ! bfd_set_section_alignment (abfd, prop_sec, 2))
|
|
{
|
|
*error_message = _("could not create new section");
|
|
goto cleanup;
|
|
}
|
|
|
|
prop_sec->size = entry_count * 12;
|
|
prop_contents = (bfd_byte *) bfd_zalloc (abfd, prop_sec->size);
|
|
elf_section_data (prop_sec)->this_hdr.contents = prop_contents;
|
|
|
|
/* The entry size and size must be set to allow the linker to compute
|
|
the number of relocations since it does not use reloc_count. */
|
|
rel_hdr = _bfd_elf_single_rel_hdr (prop_sec);
|
|
rel_hdr->sh_entsize = sizeof (Elf32_External_Rela);
|
|
rel_hdr->sh_size = _bfd_elf_single_rel_hdr (insn_sec)->sh_size;
|
|
|
|
if (prop_contents == NULL && prop_sec->size != 0)
|
|
{
|
|
*error_message = _("could not allocate section contents");
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Read the relocations. */
|
|
reloc_count = insn_sec->reloc_count;
|
|
if (reloc_count != 0)
|
|
{
|
|
/* If there is already an internal_reloc, then save it so that the
|
|
read_relocs function freshly allocates a copy. */
|
|
Elf_Internal_Rela *saved_relocs = elf_section_data (insn_sec)->relocs;
|
|
|
|
elf_section_data (insn_sec)->relocs = NULL;
|
|
internal_relocs =
|
|
_bfd_elf_link_read_relocs (abfd, insn_sec, NULL, NULL, FALSE);
|
|
elf_section_data (insn_sec)->relocs = saved_relocs;
|
|
|
|
if (internal_relocs == NULL)
|
|
{
|
|
*error_message = _("out of memory");
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/* Create a relocation section for the property section. */
|
|
if (internal_relocs != NULL)
|
|
{
|
|
elf_section_data (prop_sec)->relocs = internal_relocs;
|
|
prop_sec->reloc_count = reloc_count;
|
|
}
|
|
|
|
/* Now copy each insn table entry to the prop table entry with
|
|
appropriate flags. */
|
|
for (entry = 0; entry < entry_count; ++entry)
|
|
{
|
|
unsigned value;
|
|
unsigned flags = (XTENSA_PROP_INSN | XTENSA_PROP_NO_TRANSFORM
|
|
| XTENSA_PROP_INSN_NO_REORDER);
|
|
value = bfd_get_32 (abfd, insn_contents + entry * 8 + 0);
|
|
bfd_put_32 (abfd, value, prop_contents + entry * 12 + 0);
|
|
value = bfd_get_32 (abfd, insn_contents + entry * 8 + 4);
|
|
bfd_put_32 (abfd, value, prop_contents + entry * 12 + 4);
|
|
bfd_put_32 (abfd, flags, prop_contents + entry * 12 + 8);
|
|
}
|
|
|
|
/* Now copy all of the relocations. Change offsets for the
|
|
instruction table section to offsets in the property table
|
|
section. */
|
|
if (internal_relocs)
|
|
{
|
|
unsigned i;
|
|
|
|
for (i = 0; i < reloc_count; i++)
|
|
{
|
|
Elf_Internal_Rela *rela;
|
|
unsigned r_offset;
|
|
|
|
rela = &internal_relocs[i];
|
|
|
|
/* If this relocation is to the .xt.insn section,
|
|
change the section number and the offset. */
|
|
r_offset = rela->r_offset;
|
|
r_offset += 4 * (r_offset / 8);
|
|
rela->r_offset = r_offset;
|
|
}
|
|
}
|
|
|
|
remove_section (abfd, insn_sec);
|
|
|
|
if (insn_contents)
|
|
free (insn_contents);
|
|
|
|
return TRUE;
|
|
|
|
cleanup:
|
|
if (prop_sec && prop_sec->owner)
|
|
remove_section (abfd, prop_sec);
|
|
if (insn_contents)
|
|
free (insn_contents);
|
|
if (internal_relocs)
|
|
free (internal_relocs);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
#define PROP_SEC_BASE_NAME ".xt.prop"
|
|
#define INSN_SEC_BASE_NAME ".xt.insn"
|
|
#define LINKONCE_SEC_OLD_TEXT_BASE_NAME ".gnu.linkonce.x."
|
|
|
|
|
|
static void
|
|
replace_instruction_table_sections (bfd *abfd, asection *sec)
|
|
{
|
|
char *message = "";
|
|
const char *insn_sec_name = NULL;
|
|
char *prop_sec_name = NULL;
|
|
char *owned_prop_sec_name = NULL;
|
|
const char *sec_name;
|
|
|
|
sec_name = bfd_get_section_name (abfd, sec);
|
|
if (strcmp (sec_name, INSN_SEC_BASE_NAME) == 0)
|
|
{
|
|
insn_sec_name = INSN_SEC_BASE_NAME;
|
|
prop_sec_name = PROP_SEC_BASE_NAME;
|
|
}
|
|
else if (CONST_STRNEQ (sec_name, LINKONCE_SEC_OLD_TEXT_BASE_NAME))
|
|
{
|
|
insn_sec_name = sec_name;
|
|
owned_prop_sec_name = (char *) xmalloc (strlen (sec_name) + 20);
|
|
prop_sec_name = owned_prop_sec_name;
|
|
strcpy (prop_sec_name, ".gnu.linkonce.prop.t.");
|
|
strcat (prop_sec_name,
|
|
sec_name + strlen (LINKONCE_SEC_OLD_TEXT_BASE_NAME));
|
|
}
|
|
if (insn_sec_name != NULL)
|
|
{
|
|
if (! replace_insn_sec_with_prop_sec (abfd, insn_sec_name, prop_sec_name,
|
|
&message))
|
|
{
|
|
einfo (_("%P: warning: failed to convert %s table in %B (%s); subsequent disassembly may be incomplete\n"),
|
|
insn_sec_name, abfd, message);
|
|
}
|
|
}
|
|
if (owned_prop_sec_name)
|
|
free (owned_prop_sec_name);
|
|
}
|
|
|
|
|
|
/* This is called after all input sections have been opened to convert
|
|
instruction tables (.xt.insn, gnu.linkonce.x.*) tables into property
|
|
tables (.xt.prop) before any section placement. */
|
|
|
|
static void
|
|
elf_xtensa_after_open (void)
|
|
{
|
|
/* First call the ELF version. */
|
|
gld${EMULATION_NAME}_after_open ();
|
|
|
|
/* Now search the input files looking for instruction table sections. */
|
|
LANG_FOR_EACH_INPUT_STATEMENT (f)
|
|
{
|
|
asection *sec = f->the_bfd->sections;
|
|
asection *next_sec;
|
|
|
|
/* Do not use bfd_map_over_sections here since we are removing
|
|
sections as we iterate. */
|
|
while (sec != NULL)
|
|
{
|
|
next_sec = sec->next;
|
|
replace_instruction_table_sections (f->the_bfd, sec);
|
|
sec = next_sec;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static bfd_boolean
|
|
xt_config_info_unpack_and_check (char *data,
|
|
bfd_boolean *pmismatch,
|
|
char **pmsg)
|
|
{
|
|
char *d, *key;
|
|
unsigned num;
|
|
|
|
*pmismatch = FALSE;
|
|
|
|
d = data;
|
|
while (*d)
|
|
{
|
|
key = d;
|
|
d = strchr (d, '=');
|
|
if (! d)
|
|
goto error;
|
|
|
|
/* Overwrite the equal sign. */
|
|
*d++ = 0;
|
|
|
|
/* Check if this is a quoted string or a number. */
|
|
if (*d == '"')
|
|
{
|
|
/* No string values are currently checked by LD;
|
|
just skip over the quotes. */
|
|
d++;
|
|
d = strchr (d, '"');
|
|
if (! d)
|
|
goto error;
|
|
/* Overwrite the trailing quote. */
|
|
*d++ = 0;
|
|
}
|
|
else
|
|
{
|
|
if (*d == 0)
|
|
goto error;
|
|
num = strtoul (d, &d, 0);
|
|
|
|
if (! strcmp (key, "ABI"))
|
|
{
|
|
if (num != XSHAL_ABI)
|
|
{
|
|
*pmismatch = TRUE;
|
|
*pmsg = "ABI does not match";
|
|
}
|
|
}
|
|
else if (! strcmp (key, "USE_ABSOLUTE_LITERALS"))
|
|
{
|
|
if (num != XSHAL_USE_ABSOLUTE_LITERALS)
|
|
{
|
|
*pmismatch = TRUE;
|
|
*pmsg = "incompatible use of the Extended L32R option";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (*d++ != '\n')
|
|
goto error;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
error:
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
#define XTINFO_NAME "Xtensa_Info"
|
|
#define XTINFO_NAMESZ 12
|
|
#define XTINFO_TYPE 1
|
|
|
|
static void
|
|
check_xtensa_info (bfd *abfd, asection *info_sec)
|
|
{
|
|
char *data, *errmsg = "";
|
|
bfd_boolean mismatch;
|
|
|
|
data = xmalloc (info_sec->size);
|
|
if (! bfd_get_section_contents (abfd, info_sec, data, 0, info_sec->size))
|
|
einfo (_("%F%P:%B: cannot read contents of section %A\n"), abfd, info_sec);
|
|
|
|
if (info_sec->size > 24
|
|
&& info_sec->size >= 24 + bfd_get_32 (abfd, data + 4)
|
|
&& bfd_get_32 (abfd, data + 0) == XTINFO_NAMESZ
|
|
&& bfd_get_32 (abfd, data + 8) == XTINFO_TYPE
|
|
&& strcmp (data + 12, XTINFO_NAME) == 0
|
|
&& xt_config_info_unpack_and_check (data + 12 + XTINFO_NAMESZ,
|
|
&mismatch, &errmsg))
|
|
{
|
|
if (mismatch)
|
|
einfo (_("%P:%B: warning: incompatible Xtensa configuration (%s)\n"),
|
|
abfd, errmsg);
|
|
}
|
|
else
|
|
einfo (_("%P:%B: warning: cannot parse .xtensa.info section\n"), abfd);
|
|
|
|
free (data);
|
|
}
|
|
|
|
|
|
/* This is called after the sections have been attached to output
|
|
sections, but before any sizes or addresses have been set. */
|
|
|
|
static void
|
|
elf_xtensa_before_allocation (void)
|
|
{
|
|
asection *info_sec, *first_info_sec;
|
|
bfd *first_bfd;
|
|
bfd_boolean is_big_endian = XCHAL_HAVE_BE;
|
|
|
|
/* Check that the output endianness matches the Xtensa
|
|
configuration. The BFD library always includes both big and
|
|
little endian target vectors for Xtensa, but it only supports the
|
|
detailed instruction encode/decode operations (such as are
|
|
required to process relocations) for the selected Xtensa
|
|
configuration. */
|
|
|
|
if (is_big_endian
|
|
&& link_info.output_bfd->xvec->byteorder == BFD_ENDIAN_LITTLE)
|
|
{
|
|
einfo (_("%F%P: little endian output does not match "
|
|
"Xtensa configuration\n"));
|
|
}
|
|
if (!is_big_endian
|
|
&& link_info.output_bfd->xvec->byteorder == BFD_ENDIAN_BIG)
|
|
{
|
|
einfo (_("%F%P: big endian output does not match "
|
|
"Xtensa configuration\n"));
|
|
}
|
|
|
|
/* Keep track of the first input .xtensa.info section, and as a fallback,
|
|
the first input bfd where a .xtensa.info section could be created.
|
|
After the input .xtensa.info has been checked, the contents of the
|
|
first one will be replaced with the output .xtensa.info table. */
|
|
first_info_sec = 0;
|
|
first_bfd = 0;
|
|
|
|
LANG_FOR_EACH_INPUT_STATEMENT (f)
|
|
{
|
|
/* Check that the endianness for each input file matches the output.
|
|
The merge_private_bfd_data hook has already reported any mismatches
|
|
as errors, but those errors are not fatal. At this point, we
|
|
cannot go any further if there are any mismatches. */
|
|
if ((is_big_endian && f->the_bfd->xvec->byteorder == BFD_ENDIAN_LITTLE)
|
|
|| (!is_big_endian && f->the_bfd->xvec->byteorder == BFD_ENDIAN_BIG))
|
|
einfo (_("%F%P: cross-endian linking for %B not supported\n"),
|
|
f->the_bfd);
|
|
|
|
if (! first_bfd)
|
|
first_bfd = f->the_bfd;
|
|
|
|
info_sec = bfd_get_section_by_name (f->the_bfd, ".xtensa.info");
|
|
if (! info_sec)
|
|
continue;
|
|
|
|
if (! first_info_sec)
|
|
first_info_sec = info_sec;
|
|
|
|
/* Unpack the .xtensa.info section and check it against the current
|
|
Xtensa configuration. */
|
|
check_xtensa_info (f->the_bfd, info_sec);
|
|
|
|
/* Do not include this copy of .xtensa.info in the output. */
|
|
info_sec->size = 0;
|
|
info_sec->flags |= SEC_EXCLUDE;
|
|
}
|
|
|
|
/* Reuse the first .xtensa.info input section to hold the output
|
|
.xtensa.info; or, if none were found, create a new section in the
|
|
first input bfd (assuming there is one). */
|
|
info_sec = first_info_sec;
|
|
if (! info_sec && first_bfd)
|
|
{
|
|
info_sec = bfd_make_section_with_flags (first_bfd, ".xtensa.info",
|
|
SEC_HAS_CONTENTS | SEC_READONLY);
|
|
if (! info_sec)
|
|
einfo (_("%F%P: failed to create .xtensa.info section\n"));
|
|
}
|
|
if (info_sec)
|
|
{
|
|
int xtensa_info_size;
|
|
char *data;
|
|
|
|
info_sec->flags &= ~SEC_EXCLUDE;
|
|
info_sec->flags |= SEC_IN_MEMORY;
|
|
|
|
data = xmalloc (100);
|
|
sprintf (data, "USE_ABSOLUTE_LITERALS=%d\nABI=%d\n",
|
|
XSHAL_USE_ABSOLUTE_LITERALS, XSHAL_ABI);
|
|
xtensa_info_size = strlen (data) + 1;
|
|
|
|
/* Add enough null terminators to pad to a word boundary. */
|
|
do
|
|
data[xtensa_info_size++] = 0;
|
|
while ((xtensa_info_size & 3) != 0);
|
|
|
|
info_sec->size = 12 + XTINFO_NAMESZ + xtensa_info_size;
|
|
info_sec->contents = xmalloc (info_sec->size);
|
|
bfd_put_32 (info_sec->owner, XTINFO_NAMESZ, info_sec->contents + 0);
|
|
bfd_put_32 (info_sec->owner, xtensa_info_size, info_sec->contents + 4);
|
|
bfd_put_32 (info_sec->owner, XTINFO_TYPE, info_sec->contents + 8);
|
|
memcpy (info_sec->contents + 12, XTINFO_NAME, XTINFO_NAMESZ);
|
|
memcpy (info_sec->contents + 12 + XTINFO_NAMESZ, data, xtensa_info_size);
|
|
free (data);
|
|
}
|
|
|
|
/* Enable relaxation by default if the "--no-relax" option was not
|
|
specified. This is done here instead of in the before_parse hook
|
|
because there is a check in main() to prohibit use of --relax and
|
|
-r together and that combination should be allowed for Xtensa. */
|
|
if (RELAXATION_DISABLED_BY_DEFAULT)
|
|
ENABLE_RELAXATION;
|
|
|
|
xtensa_strip_inconsistent_linkonce_sections (stat_ptr);
|
|
|
|
gld${EMULATION_NAME}_before_allocation ();
|
|
|
|
xtensa_wild_group_interleave (stat_ptr->head);
|
|
|
|
if (RELAXATION_ENABLED)
|
|
xtensa_colocate_output_literals (stat_ptr->head);
|
|
|
|
/* TBD: We need to force the page alignments to here and only do
|
|
them as needed for the entire output section. Finally, if this
|
|
is a relocatable link then we need to add alignment notes so
|
|
that the literals can be separated later. */
|
|
}
|
|
|
|
|
|
typedef struct wildcard_list section_name_list;
|
|
|
|
typedef struct reloc_deps_e_t reloc_deps_e;
|
|
typedef struct reloc_deps_section_t reloc_deps_section;
|
|
typedef struct reloc_deps_graph_t reloc_deps_graph;
|
|
|
|
|
|
struct reloc_deps_e_t
|
|
{
|
|
asection *src; /* Contains l32rs. */
|
|
asection *tgt; /* Contains literals. */
|
|
reloc_deps_e *next;
|
|
};
|
|
|
|
/* Place these in the userdata field. */
|
|
struct reloc_deps_section_t
|
|
{
|
|
reloc_deps_e *preds;
|
|
reloc_deps_e *succs;
|
|
bfd_boolean is_only_literal;
|
|
};
|
|
|
|
|
|
struct reloc_deps_graph_t
|
|
{
|
|
size_t count;
|
|
size_t size;
|
|
asection **sections;
|
|
};
|
|
|
|
static void xtensa_layout_wild
|
|
(const reloc_deps_graph *, lang_wild_statement_type *);
|
|
|
|
typedef void (*deps_callback_t) (asection *, /* src_sec */
|
|
bfd_vma, /* src_offset */
|
|
asection *, /* target_sec */
|
|
bfd_vma, /* target_offset */
|
|
void *); /* closure */
|
|
|
|
extern bfd_boolean xtensa_callback_required_dependence
|
|
(bfd *, asection *, struct bfd_link_info *, deps_callback_t, void *);
|
|
static void xtensa_ldlang_clear_addresses (lang_statement_union_type *);
|
|
static bfd_boolean ld_local_file_relocations_fit
|
|
(lang_statement_union_type *, const reloc_deps_graph *);
|
|
static bfd_vma ld_assign_relative_paged_dot
|
|
(bfd_vma, lang_statement_union_type *, const reloc_deps_graph *,
|
|
bfd_boolean);
|
|
static bfd_vma ld_xtensa_insert_page_offsets
|
|
(bfd_vma, lang_statement_union_type *, reloc_deps_graph *, bfd_boolean);
|
|
#if EXTRA_VALIDATION
|
|
static size_t ld_count_children (lang_statement_union_type *);
|
|
#endif
|
|
|
|
extern lang_statement_list_type constructor_list;
|
|
|
|
static reloc_deps_section *
|
|
xtensa_get_section_deps (const reloc_deps_graph *deps ATTRIBUTE_UNUSED,
|
|
asection *sec)
|
|
{
|
|
/* We have a separate function for this so that
|
|
we could in the future keep a completely independent
|
|
structure that maps a section to its dependence edges.
|
|
For now, we place these in the sec->userdata field. */
|
|
reloc_deps_section *sec_deps = sec->userdata;
|
|
return sec_deps;
|
|
}
|
|
|
|
static void
|
|
xtensa_set_section_deps (const reloc_deps_graph *deps ATTRIBUTE_UNUSED,
|
|
asection *sec,
|
|
reloc_deps_section *deps_section)
|
|
{
|
|
sec->userdata = deps_section;
|
|
}
|
|
|
|
|
|
/* This is used to keep a list of all of the sections participating in
|
|
the graph so we can clean them up quickly. */
|
|
|
|
static void
|
|
xtensa_append_section_deps (reloc_deps_graph *deps, asection *sec)
|
|
{
|
|
if (deps->size <= deps->count)
|
|
{
|
|
asection **new_sections;
|
|
size_t i;
|
|
size_t new_size;
|
|
|
|
new_size = deps->size * 2;
|
|
if (new_size == 0)
|
|
new_size = 20;
|
|
|
|
new_sections = xmalloc (sizeof (asection *) * new_size);
|
|
memset (new_sections, 0, sizeof (asection *) * new_size);
|
|
for (i = 0; i < deps->count; i++)
|
|
{
|
|
new_sections[i] = deps->sections[i];
|
|
}
|
|
if (deps->sections != NULL)
|
|
free (deps->sections);
|
|
deps->sections = new_sections;
|
|
deps->size = new_size;
|
|
}
|
|
deps->sections[deps->count] = sec;
|
|
deps->count++;
|
|
}
|
|
|
|
|
|
static void
|
|
free_reloc_deps_graph (reloc_deps_graph *deps)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < deps->count; i++)
|
|
{
|
|
asection *sec = deps->sections[i];
|
|
reloc_deps_section *sec_deps;
|
|
sec_deps = xtensa_get_section_deps (deps, sec);
|
|
if (sec_deps)
|
|
{
|
|
reloc_deps_e *next;
|
|
while (sec_deps->succs != NULL)
|
|
{
|
|
next = sec_deps->succs->next;
|
|
free (sec_deps->succs);
|
|
sec_deps->succs = next;
|
|
}
|
|
|
|
while (sec_deps->preds != NULL)
|
|
{
|
|
next = sec_deps->preds->next;
|
|
free (sec_deps->preds);
|
|
sec_deps->preds = next;
|
|
}
|
|
free (sec_deps);
|
|
}
|
|
xtensa_set_section_deps (deps, sec, NULL);
|
|
}
|
|
if (deps->sections)
|
|
free (deps->sections);
|
|
|
|
free (deps);
|
|
}
|
|
|
|
|
|
static bfd_boolean
|
|
section_is_source (const reloc_deps_graph *deps ATTRIBUTE_UNUSED,
|
|
lang_statement_union_type *s)
|
|
{
|
|
asection *sec;
|
|
const reloc_deps_section *sec_deps;
|
|
|
|
if (s->header.type != lang_input_section_enum)
|
|
return FALSE;
|
|
sec = s->input_section.section;
|
|
|
|
sec_deps = xtensa_get_section_deps (deps, sec);
|
|
return sec_deps && sec_deps->succs != NULL;
|
|
}
|
|
|
|
|
|
static bfd_boolean
|
|
section_is_target (const reloc_deps_graph *deps ATTRIBUTE_UNUSED,
|
|
lang_statement_union_type *s)
|
|
{
|
|
asection *sec;
|
|
const reloc_deps_section *sec_deps;
|
|
|
|
if (s->header.type != lang_input_section_enum)
|
|
return FALSE;
|
|
sec = s->input_section.section;
|
|
|
|
sec_deps = xtensa_get_section_deps (deps, sec);
|
|
return sec_deps && sec_deps->preds != NULL;
|
|
}
|
|
|
|
|
|
static bfd_boolean
|
|
section_is_source_or_target (const reloc_deps_graph *deps ATTRIBUTE_UNUSED,
|
|
lang_statement_union_type *s)
|
|
{
|
|
return (section_is_source (deps, s)
|
|
|| section_is_target (deps, s));
|
|
}
|
|
|
|
|
|
typedef struct xtensa_ld_iter_stack_t xtensa_ld_iter_stack;
|
|
typedef struct xtensa_ld_iter_t xtensa_ld_iter;
|
|
|
|
struct xtensa_ld_iter_t
|
|
{
|
|
lang_statement_union_type *parent; /* Parent of the list. */
|
|
lang_statement_list_type *l; /* List that holds it. */
|
|
lang_statement_union_type **loc; /* Place in the list. */
|
|
};
|
|
|
|
struct xtensa_ld_iter_stack_t
|
|
{
|
|
xtensa_ld_iter iterloc; /* List that hold it. */
|
|
|
|
xtensa_ld_iter_stack *next; /* Next in the stack. */
|
|
xtensa_ld_iter_stack *prev; /* Back pointer for stack. */
|
|
};
|
|
|
|
|
|
static void
|
|
ld_xtensa_move_section_after (xtensa_ld_iter *to, xtensa_ld_iter *current)
|
|
{
|
|
lang_statement_union_type *to_next;
|
|
lang_statement_union_type *current_next;
|
|
lang_statement_union_type **e;
|
|
|
|
#if EXTRA_VALIDATION
|
|
size_t old_to_count, new_to_count;
|
|
size_t old_current_count, new_current_count;
|
|
#endif
|
|
|
|
if (to == current)
|
|
return;
|
|
|
|
#if EXTRA_VALIDATION
|
|
old_to_count = ld_count_children (to->parent);
|
|
old_current_count = ld_count_children (current->parent);
|
|
#endif
|
|
|
|
to_next = *(to->loc);
|
|
current_next = (*current->loc)->header.next;
|
|
|
|
*(to->loc) = *(current->loc);
|
|
|
|
*(current->loc) = current_next;
|
|
(*(to->loc))->header.next = to_next;
|
|
|
|
/* reset "to" list tail */
|
|
for (e = &to->l->head; *e != NULL; e = &(*e)->header.next)
|
|
;
|
|
to->l->tail = e;
|
|
|
|
/* reset "current" list tail */
|
|
for (e = ¤t->l->head; *e != NULL; e = &(*e)->header.next)
|
|
;
|
|
current->l->tail = e;
|
|
|
|
#if EXTRA_VALIDATION
|
|
new_to_count = ld_count_children (to->parent);
|
|
new_current_count = ld_count_children (current->parent);
|
|
|
|
ASSERT ((old_to_count + old_current_count)
|
|
== (new_to_count + new_current_count));
|
|
#endif
|
|
}
|
|
|
|
|
|
/* Can only be called with lang_statements that have lists. Returns
|
|
FALSE if the list is empty. */
|
|
|
|
static bfd_boolean
|
|
iter_stack_empty (xtensa_ld_iter_stack **stack_p)
|
|
{
|
|
return *stack_p == NULL;
|
|
}
|
|
|
|
|
|
static bfd_boolean
|
|
iter_stack_push (xtensa_ld_iter_stack **stack_p,
|
|
lang_statement_union_type *parent)
|
|
{
|
|
xtensa_ld_iter_stack *stack;
|
|
lang_statement_list_type *l = NULL;
|
|
|
|
switch (parent->header.type)
|
|
{
|
|
case lang_output_section_statement_enum:
|
|
l = &parent->output_section_statement.children;
|
|
break;
|
|
case lang_wild_statement_enum:
|
|
l = &parent->wild_statement.children;
|
|
break;
|
|
case lang_group_statement_enum:
|
|
l = &parent->group_statement.children;
|
|
break;
|
|
default:
|
|
ASSERT (0);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Empty. do not push. */
|
|
if (l->tail == &l->head)
|
|
return FALSE;
|
|
|
|
stack = xmalloc (sizeof (xtensa_ld_iter_stack));
|
|
memset (stack, 0, sizeof (xtensa_ld_iter_stack));
|
|
stack->iterloc.parent = parent;
|
|
stack->iterloc.l = l;
|
|
stack->iterloc.loc = &l->head;
|
|
|
|
stack->next = *stack_p;
|
|
stack->prev = NULL;
|
|
if (*stack_p != NULL)
|
|
(*stack_p)->prev = stack;
|
|
*stack_p = stack;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static void
|
|
iter_stack_pop (xtensa_ld_iter_stack **stack_p)
|
|
{
|
|
xtensa_ld_iter_stack *stack;
|
|
|
|
stack = *stack_p;
|
|
|
|
if (stack == NULL)
|
|
{
|
|
ASSERT (stack != NULL);
|
|
return;
|
|
}
|
|
|
|
if (stack->next != NULL)
|
|
stack->next->prev = NULL;
|
|
|
|
*stack_p = stack->next;
|
|
free (stack);
|
|
}
|
|
|
|
|
|
/* This MUST be called if, during iteration, the user changes the
|
|
underlying structure. It will check for a NULL current and advance
|
|
accordingly. */
|
|
|
|
static void
|
|
iter_stack_update (xtensa_ld_iter_stack **stack_p)
|
|
{
|
|
if (!iter_stack_empty (stack_p)
|
|
&& (*(*stack_p)->iterloc.loc) == NULL)
|
|
{
|
|
iter_stack_pop (stack_p);
|
|
|
|
while (!iter_stack_empty (stack_p)
|
|
&& ((*(*stack_p)->iterloc.loc)->header.next == NULL))
|
|
{
|
|
iter_stack_pop (stack_p);
|
|
}
|
|
if (!iter_stack_empty (stack_p))
|
|
(*stack_p)->iterloc.loc = &(*(*stack_p)->iterloc.loc)->header.next;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
iter_stack_next (xtensa_ld_iter_stack **stack_p)
|
|
{
|
|
xtensa_ld_iter_stack *stack;
|
|
lang_statement_union_type *current;
|
|
stack = *stack_p;
|
|
|
|
current = *stack->iterloc.loc;
|
|
/* If we are on the first element. */
|
|
if (current != NULL)
|
|
{
|
|
switch (current->header.type)
|
|
{
|
|
case lang_output_section_statement_enum:
|
|
case lang_wild_statement_enum:
|
|
case lang_group_statement_enum:
|
|
/* If the list if not empty, we are done. */
|
|
if (iter_stack_push (stack_p, *stack->iterloc.loc))
|
|
return;
|
|
/* Otherwise increment the pointer as normal. */
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
while (!iter_stack_empty (stack_p)
|
|
&& ((*(*stack_p)->iterloc.loc)->header.next == NULL))
|
|
{
|
|
iter_stack_pop (stack_p);
|
|
}
|
|
if (!iter_stack_empty (stack_p))
|
|
(*stack_p)->iterloc.loc = &(*(*stack_p)->iterloc.loc)->header.next;
|
|
}
|
|
|
|
|
|
static lang_statement_union_type *
|
|
iter_stack_current (xtensa_ld_iter_stack **stack_p)
|
|
{
|
|
return *((*stack_p)->iterloc.loc);
|
|
}
|
|
|
|
|
|
/* The iter stack is a preorder. */
|
|
|
|
static void
|
|
iter_stack_create (xtensa_ld_iter_stack **stack_p,
|
|
lang_statement_union_type *parent)
|
|
{
|
|
iter_stack_push (stack_p, parent);
|
|
}
|
|
|
|
|
|
static void
|
|
iter_stack_copy_current (xtensa_ld_iter_stack **stack_p, xtensa_ld_iter *front)
|
|
{
|
|
*front = (*stack_p)->iterloc;
|
|
}
|
|
|
|
|
|
static void
|
|
xtensa_colocate_literals (reloc_deps_graph *deps,
|
|
lang_statement_union_type *statement)
|
|
{
|
|
/* Keep a stack of pointers to control iteration through the contours. */
|
|
xtensa_ld_iter_stack *stack = NULL;
|
|
xtensa_ld_iter_stack **stack_p = &stack;
|
|
|
|
xtensa_ld_iter front; /* Location where new insertion should occur. */
|
|
xtensa_ld_iter *front_p = NULL;
|
|
|
|
xtensa_ld_iter current; /* Location we are checking. */
|
|
xtensa_ld_iter *current_p = NULL;
|
|
bfd_boolean in_literals = FALSE;
|
|
|
|
if (deps->count == 0)
|
|
return;
|
|
|
|
iter_stack_create (stack_p, statement);
|
|
|
|
while (!iter_stack_empty (stack_p))
|
|
{
|
|
bfd_boolean skip_increment = FALSE;
|
|
lang_statement_union_type *l = iter_stack_current (stack_p);
|
|
|
|
switch (l->header.type)
|
|
{
|
|
case lang_assignment_statement_enum:
|
|
/* Any assignment statement should block reordering across it. */
|
|
front_p = NULL;
|
|
in_literals = FALSE;
|
|
break;
|
|
|
|
case lang_input_section_enum:
|
|
if (front_p == NULL)
|
|
{
|
|
in_literals = (section_is_target (deps, l)
|
|
&& !section_is_source (deps, l));
|
|
if (in_literals)
|
|
{
|
|
front_p = &front;
|
|
iter_stack_copy_current (stack_p, front_p);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bfd_boolean is_target;
|
|
current_p = ¤t;
|
|
iter_stack_copy_current (stack_p, current_p);
|
|
is_target = (section_is_target (deps, l)
|
|
&& !section_is_source (deps, l));
|
|
|
|
if (in_literals)
|
|
{
|
|
iter_stack_copy_current (stack_p, front_p);
|
|
if (!is_target)
|
|
in_literals = FALSE;
|
|
}
|
|
else
|
|
{
|
|
if (is_target)
|
|
{
|
|
/* Try to insert in place. */
|
|
ld_xtensa_move_section_after (front_p, current_p);
|
|
ld_assign_relative_paged_dot (0x100000,
|
|
statement,
|
|
deps,
|
|
xtensa_use_literal_pages);
|
|
|
|
/* We use this code because it's already written. */
|
|
if (!ld_local_file_relocations_fit (statement, deps))
|
|
{
|
|
/* Move it back. */
|
|
ld_xtensa_move_section_after (current_p, front_p);
|
|
/* Reset the literal placement. */
|
|
iter_stack_copy_current (stack_p, front_p);
|
|
}
|
|
else
|
|
{
|
|
/* Move front pointer up by one. */
|
|
front_p->loc = &(*front_p->loc)->header.next;
|
|
|
|
/* Do not increment the current pointer. */
|
|
skip_increment = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!skip_increment)
|
|
iter_stack_next (stack_p);
|
|
else
|
|
/* Be careful to update the stack_p if it now is a null. */
|
|
iter_stack_update (stack_p);
|
|
}
|
|
|
|
lang_for_each_statement_worker (xtensa_ldlang_clear_addresses, statement);
|
|
}
|
|
|
|
|
|
static void
|
|
xtensa_move_dependencies_to_front (reloc_deps_graph *deps,
|
|
lang_wild_statement_type *w)
|
|
{
|
|
/* Keep a front pointer and a current pointer. */
|
|
lang_statement_union_type **front;
|
|
lang_statement_union_type **current;
|
|
|
|
/* Walk to the end of the targets. */
|
|
for (front = &w->children.head;
|
|
(*front != NULL) && section_is_source_or_target (deps, *front);
|
|
front = &(*front)->header.next)
|
|
;
|
|
|
|
if (*front == NULL)
|
|
return;
|
|
|
|
current = &(*front)->header.next;
|
|
while (*current != NULL)
|
|
{
|
|
if (section_is_source_or_target (deps, *current))
|
|
{
|
|
/* Insert in place. */
|
|
xtensa_ld_iter front_iter;
|
|
xtensa_ld_iter current_iter;
|
|
|
|
front_iter.parent = (lang_statement_union_type *) w;
|
|
front_iter.l = &w->children;
|
|
front_iter.loc = front;
|
|
|
|
current_iter.parent = (lang_statement_union_type *) w;
|
|
current_iter.l = &w->children;
|
|
current_iter.loc = current;
|
|
|
|
ld_xtensa_move_section_after (&front_iter, ¤t_iter);
|
|
front = &(*front)->header.next;
|
|
}
|
|
else
|
|
{
|
|
current = &(*current)->header.next;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static bfd_boolean
|
|
deps_has_sec_edge (const reloc_deps_graph *deps, asection *src, asection *tgt)
|
|
{
|
|
const reloc_deps_section *sec_deps;
|
|
const reloc_deps_e *sec_deps_e;
|
|
|
|
sec_deps = xtensa_get_section_deps (deps, src);
|
|
if (sec_deps == NULL)
|
|
return FALSE;
|
|
|
|
for (sec_deps_e = sec_deps->succs;
|
|
sec_deps_e != NULL;
|
|
sec_deps_e = sec_deps_e->next)
|
|
{
|
|
ASSERT (sec_deps_e->src == src);
|
|
if (sec_deps_e->tgt == tgt)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static bfd_boolean
|
|
deps_has_edge (const reloc_deps_graph *deps,
|
|
lang_statement_union_type *src,
|
|
lang_statement_union_type *tgt)
|
|
{
|
|
if (!section_is_source (deps, src))
|
|
return FALSE;
|
|
if (!section_is_target (deps, tgt))
|
|
return FALSE;
|
|
|
|
if (src->header.type != lang_input_section_enum)
|
|
return FALSE;
|
|
if (tgt->header.type != lang_input_section_enum)
|
|
return FALSE;
|
|
|
|
return deps_has_sec_edge (deps, src->input_section.section,
|
|
tgt->input_section.section);
|
|
}
|
|
|
|
|
|
static void
|
|
add_deps_edge (reloc_deps_graph *deps, asection *src_sec, asection *tgt_sec)
|
|
{
|
|
reloc_deps_section *src_sec_deps;
|
|
reloc_deps_section *tgt_sec_deps;
|
|
|
|
reloc_deps_e *src_edge;
|
|
reloc_deps_e *tgt_edge;
|
|
|
|
if (deps_has_sec_edge (deps, src_sec, tgt_sec))
|
|
return;
|
|
|
|
src_sec_deps = xtensa_get_section_deps (deps, src_sec);
|
|
if (src_sec_deps == NULL)
|
|
{
|
|
/* Add a section. */
|
|
src_sec_deps = xmalloc (sizeof (reloc_deps_section));
|
|
memset (src_sec_deps, 0, sizeof (reloc_deps_section));
|
|
src_sec_deps->is_only_literal = 0;
|
|
src_sec_deps->preds = NULL;
|
|
src_sec_deps->succs = NULL;
|
|
xtensa_set_section_deps (deps, src_sec, src_sec_deps);
|
|
xtensa_append_section_deps (deps, src_sec);
|
|
}
|
|
|
|
tgt_sec_deps = xtensa_get_section_deps (deps, tgt_sec);
|
|
if (tgt_sec_deps == NULL)
|
|
{
|
|
/* Add a section. */
|
|
tgt_sec_deps = xmalloc (sizeof (reloc_deps_section));
|
|
memset (tgt_sec_deps, 0, sizeof (reloc_deps_section));
|
|
tgt_sec_deps->is_only_literal = 0;
|
|
tgt_sec_deps->preds = NULL;
|
|
tgt_sec_deps->succs = NULL;
|
|
xtensa_set_section_deps (deps, tgt_sec, tgt_sec_deps);
|
|
xtensa_append_section_deps (deps, tgt_sec);
|
|
}
|
|
|
|
/* Add the edges. */
|
|
src_edge = xmalloc (sizeof (reloc_deps_e));
|
|
memset (src_edge, 0, sizeof (reloc_deps_e));
|
|
src_edge->src = src_sec;
|
|
src_edge->tgt = tgt_sec;
|
|
src_edge->next = src_sec_deps->succs;
|
|
src_sec_deps->succs = src_edge;
|
|
|
|
tgt_edge = xmalloc (sizeof (reloc_deps_e));
|
|
memset (tgt_edge, 0, sizeof (reloc_deps_e));
|
|
tgt_edge->src = src_sec;
|
|
tgt_edge->tgt = tgt_sec;
|
|
tgt_edge->next = tgt_sec_deps->preds;
|
|
tgt_sec_deps->preds = tgt_edge;
|
|
}
|
|
|
|
|
|
static void
|
|
build_deps_graph_callback (asection *src_sec,
|
|
bfd_vma src_offset ATTRIBUTE_UNUSED,
|
|
asection *target_sec,
|
|
bfd_vma target_offset ATTRIBUTE_UNUSED,
|
|
void *closure)
|
|
{
|
|
reloc_deps_graph *deps = closure;
|
|
|
|
/* If the target is defined. */
|
|
if (target_sec != NULL)
|
|
add_deps_edge (deps, src_sec, target_sec);
|
|
}
|
|
|
|
|
|
static reloc_deps_graph *
|
|
ld_build_required_section_dependence (lang_statement_union_type *s)
|
|
{
|
|
reloc_deps_graph *deps;
|
|
xtensa_ld_iter_stack *stack = NULL;
|
|
|
|
deps = xmalloc (sizeof (reloc_deps_graph));
|
|
deps->sections = NULL;
|
|
deps->count = 0;
|
|
deps->size = 0;
|
|
|
|
for (iter_stack_create (&stack, s);
|
|
!iter_stack_empty (&stack);
|
|
iter_stack_next (&stack))
|
|
{
|
|
lang_statement_union_type *l = iter_stack_current (&stack);
|
|
|
|
if (l->header.type == lang_input_section_enum)
|
|
{
|
|
lang_input_section_type *input;
|
|
input = &l->input_section;
|
|
xtensa_callback_required_dependence (input->section->owner,
|
|
input->section,
|
|
&link_info,
|
|
/* Use the same closure. */
|
|
build_deps_graph_callback,
|
|
deps);
|
|
}
|
|
}
|
|
return deps;
|
|
}
|
|
|
|
|
|
#if EXTRA_VALIDATION
|
|
static size_t
|
|
ld_count_children (lang_statement_union_type *s)
|
|
{
|
|
size_t count = 0;
|
|
xtensa_ld_iter_stack *stack = NULL;
|
|
for (iter_stack_create (&stack, s);
|
|
!iter_stack_empty (&stack);
|
|
iter_stack_next (&stack))
|
|
{
|
|
lang_statement_union_type *l = iter_stack_current (&stack);
|
|
ASSERT (l != NULL);
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
#endif /* EXTRA_VALIDATION */
|
|
|
|
|
|
/* Check if a particular section is included in the link. This will only
|
|
be true for one instance of a particular linkonce section. */
|
|
|
|
static bfd_boolean input_section_found = FALSE;
|
|
static asection *input_section_target = NULL;
|
|
|
|
static void
|
|
input_section_linked_worker (lang_statement_union_type *statement)
|
|
{
|
|
if ((statement->header.type == lang_input_section_enum
|
|
&& (statement->input_section.section == input_section_target)))
|
|
input_section_found = TRUE;
|
|
}
|
|
|
|
static bfd_boolean
|
|
input_section_linked (asection *sec)
|
|
{
|
|
input_section_found = FALSE;
|
|
input_section_target = sec;
|
|
lang_for_each_statement_worker (input_section_linked_worker, stat_ptr->head);
|
|
return input_section_found;
|
|
}
|
|
|
|
|
|
/* Strip out any linkonce property tables or XCC exception tables where the
|
|
associated linkonce text is from a different object file. Normally,
|
|
a matching set of linkonce sections is taken from the same object file,
|
|
but sometimes the files are compiled differently so that some of the
|
|
linkonce sections are not present in all files. Stripping the
|
|
inconsistent sections like this is not completely robust -- a much
|
|
better solution is to use comdat groups. */
|
|
|
|
static int linkonce_len = sizeof (".gnu.linkonce.") - 1;
|
|
|
|
static bfd_boolean
|
|
is_inconsistent_linkonce_section (asection *sec)
|
|
{
|
|
bfd *abfd = sec->owner;
|
|
const char *sec_name = bfd_get_section_name (abfd, sec);
|
|
const char *name;
|
|
|
|
if ((bfd_get_section_flags (abfd, sec) & SEC_LINK_ONCE) == 0
|
|
|| strncmp (sec_name, ".gnu.linkonce.", linkonce_len) != 0)
|
|
return FALSE;
|
|
|
|
/* Check if this is an Xtensa property section or an exception table
|
|
for Tensilica's XCC compiler. */
|
|
name = sec_name + linkonce_len;
|
|
if (CONST_STRNEQ (name, "prop."))
|
|
name = strchr (name + 5, '.') + 1;
|
|
else if (name[1] == '.'
|
|
&& (name[0] == 'p' || name[0] == 'e' || name[0] == 'h'))
|
|
name += 2;
|
|
else
|
|
name = 0;
|
|
|
|
if (name)
|
|
{
|
|
char *dep_sec_name = xmalloc (strlen (sec_name) + 1);
|
|
asection *dep_sec;
|
|
|
|
/* Get the associated linkonce text section and check if it is
|
|
included in the link. If not, this section is inconsistent
|
|
and should be stripped. */
|
|
strcpy (dep_sec_name, ".gnu.linkonce.t.");
|
|
strcat (dep_sec_name, name);
|
|
dep_sec = bfd_get_section_by_name (abfd, dep_sec_name);
|
|
if (dep_sec == NULL || ! input_section_linked (dep_sec))
|
|
{
|
|
free (dep_sec_name);
|
|
return TRUE;
|
|
}
|
|
free (dep_sec_name);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static void
|
|
xtensa_strip_inconsistent_linkonce_sections (lang_statement_list_type *slist)
|
|
{
|
|
lang_statement_union_type **s_p = &slist->head;
|
|
while (*s_p)
|
|
{
|
|
lang_statement_union_type *s = *s_p;
|
|
lang_statement_union_type *s_next = (*s_p)->header.next;
|
|
|
|
switch (s->header.type)
|
|
{
|
|
case lang_input_section_enum:
|
|
if (is_inconsistent_linkonce_section (s->input_section.section))
|
|
{
|
|
s->input_section.section->output_section = bfd_abs_section_ptr;
|
|
*s_p = s_next;
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case lang_constructors_statement_enum:
|
|
xtensa_strip_inconsistent_linkonce_sections (&constructor_list);
|
|
break;
|
|
|
|
case lang_output_section_statement_enum:
|
|
if (s->output_section_statement.children.head)
|
|
xtensa_strip_inconsistent_linkonce_sections
|
|
(&s->output_section_statement.children);
|
|
break;
|
|
|
|
case lang_wild_statement_enum:
|
|
xtensa_strip_inconsistent_linkonce_sections
|
|
(&s->wild_statement.children);
|
|
break;
|
|
|
|
case lang_group_statement_enum:
|
|
xtensa_strip_inconsistent_linkonce_sections
|
|
(&s->group_statement.children);
|
|
break;
|
|
|
|
case lang_data_statement_enum:
|
|
case lang_reloc_statement_enum:
|
|
case lang_object_symbols_statement_enum:
|
|
case lang_output_statement_enum:
|
|
case lang_target_statement_enum:
|
|
case lang_input_statement_enum:
|
|
case lang_assignment_statement_enum:
|
|
case lang_padding_statement_enum:
|
|
case lang_address_statement_enum:
|
|
case lang_fill_statement_enum:
|
|
break;
|
|
|
|
default:
|
|
FAIL ();
|
|
break;
|
|
}
|
|
|
|
s_p = &(*s_p)->header.next;
|
|
}
|
|
|
|
/* Reset the tail of the list, in case the last entry was removed. */
|
|
if (s_p != slist->tail)
|
|
slist->tail = s_p;
|
|
}
|
|
|
|
|
|
static void
|
|
xtensa_wild_group_interleave_callback (lang_statement_union_type *statement)
|
|
{
|
|
lang_wild_statement_type *w;
|
|
reloc_deps_graph *deps;
|
|
if (statement->header.type == lang_wild_statement_enum)
|
|
{
|
|
#if EXTRA_VALIDATION
|
|
size_t old_child_count;
|
|
size_t new_child_count;
|
|
#endif
|
|
bfd_boolean no_reorder;
|
|
|
|
w = &statement->wild_statement;
|
|
|
|
no_reorder = FALSE;
|
|
|
|
/* If it has 0 or 1 section bound, then do not reorder. */
|
|
if (w->children.head == NULL
|
|
|| (w->children.head->header.type == lang_input_section_enum
|
|
&& w->children.head->header.next == NULL))
|
|
no_reorder = TRUE;
|
|
|
|
if (w->filenames_sorted)
|
|
no_reorder = TRUE;
|
|
|
|
/* Check for sorting in a section list wildcard spec as well. */
|
|
if (!no_reorder)
|
|
{
|
|
struct wildcard_list *l;
|
|
for (l = w->section_list; l != NULL; l = l->next)
|
|
{
|
|
if (l->spec.sorted == TRUE)
|
|
{
|
|
no_reorder = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Special case until the NOREORDER linker directive is supported:
|
|
*(.init) output sections and *(.fini) specs may NOT be reordered. */
|
|
|
|
/* Check for sorting in a section list wildcard spec as well. */
|
|
if (!no_reorder)
|
|
{
|
|
struct wildcard_list *l;
|
|
for (l = w->section_list; l != NULL; l = l->next)
|
|
{
|
|
if (l->spec.name
|
|
&& ((strcmp (".init", l->spec.name) == 0)
|
|
|| (strcmp (".fini", l->spec.name) == 0)))
|
|
{
|
|
no_reorder = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if EXTRA_VALIDATION
|
|
old_child_count = ld_count_children (statement);
|
|
#endif
|
|
|
|
/* It is now officially a target. Build the graph of source
|
|
section -> target section (kept as a list of edges). */
|
|
deps = ld_build_required_section_dependence (statement);
|
|
|
|
/* If this wildcard does not reorder.... */
|
|
if (!no_reorder && deps->count != 0)
|
|
{
|
|
/* First check for reverse dependences. Fix if possible. */
|
|
xtensa_layout_wild (deps, w);
|
|
|
|
xtensa_move_dependencies_to_front (deps, w);
|
|
#if EXTRA_VALIDATION
|
|
new_child_count = ld_count_children (statement);
|
|
ASSERT (new_child_count == old_child_count);
|
|
#endif
|
|
|
|
xtensa_colocate_literals (deps, statement);
|
|
|
|
#if EXTRA_VALIDATION
|
|
new_child_count = ld_count_children (statement);
|
|
ASSERT (new_child_count == old_child_count);
|
|
#endif
|
|
}
|
|
|
|
/* Clean up. */
|
|
free_reloc_deps_graph (deps);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
xtensa_wild_group_interleave (lang_statement_union_type *s)
|
|
{
|
|
lang_for_each_statement_worker (xtensa_wild_group_interleave_callback, s);
|
|
}
|
|
|
|
|
|
static void
|
|
xtensa_layout_wild (const reloc_deps_graph *deps, lang_wild_statement_type *w)
|
|
{
|
|
/* If it does not fit initially, we need to do this step. Move all
|
|
of the wild literal sections to a new list, then move each of
|
|
them back in just before the first section they depend on. */
|
|
lang_statement_union_type **s_p;
|
|
#if EXTRA_VALIDATION
|
|
size_t old_count, new_count;
|
|
size_t ct1, ct2;
|
|
#endif
|
|
|
|
lang_wild_statement_type literal_wild;
|
|
literal_wild.header.next = NULL;
|
|
literal_wild.header.type = lang_wild_statement_enum;
|
|
literal_wild.filename = NULL;
|
|
literal_wild.filenames_sorted = FALSE;
|
|
literal_wild.section_list = NULL;
|
|
literal_wild.keep_sections = FALSE;
|
|
literal_wild.children.head = NULL;
|
|
literal_wild.children.tail = &literal_wild.children.head;
|
|
|
|
#if EXTRA_VALIDATION
|
|
old_count = ld_count_children ((lang_statement_union_type*) w);
|
|
#endif
|
|
|
|
s_p = &w->children.head;
|
|
while (*s_p != NULL)
|
|
{
|
|
lang_statement_union_type *l = *s_p;
|
|
if (l->header.type == lang_input_section_enum)
|
|
{
|
|
if (section_is_target (deps, l)
|
|
&& ! section_is_source (deps, l))
|
|
{
|
|
/* Detach. */
|
|
*s_p = l->header.next;
|
|
if (*s_p == NULL)
|
|
w->children.tail = s_p;
|
|
l->header.next = NULL;
|
|
|
|
/* Append. */
|
|
*literal_wild.children.tail = l;
|
|
literal_wild.children.tail = &l->header.next;
|
|
continue;
|
|
}
|
|
}
|
|
s_p = &(*s_p)->header.next;
|
|
}
|
|
|
|
#if EXTRA_VALIDATION
|
|
ct1 = ld_count_children ((lang_statement_union_type*) w);
|
|
ct2 = ld_count_children ((lang_statement_union_type*) &literal_wild);
|
|
|
|
ASSERT (old_count == (ct1 + ct2));
|
|
#endif
|
|
|
|
/* Now place them back in front of their dependent sections. */
|
|
|
|
while (literal_wild.children.head != NULL)
|
|
{
|
|
lang_statement_union_type *lit = literal_wild.children.head;
|
|
bfd_boolean placed = FALSE;
|
|
|
|
#if EXTRA_VALIDATION
|
|
ASSERT (ct2 > 0);
|
|
ct2--;
|
|
#endif
|
|
|
|
/* Detach. */
|
|
literal_wild.children.head = lit->header.next;
|
|
if (literal_wild.children.head == NULL)
|
|
literal_wild.children.tail = &literal_wild.children.head;
|
|
lit->header.next = NULL;
|
|
|
|
/* Find a spot to place it. */
|
|
for (s_p = &w->children.head; *s_p != NULL; s_p = &(*s_p)->header.next)
|
|
{
|
|
lang_statement_union_type *src = *s_p;
|
|
if (deps_has_edge (deps, src, lit))
|
|
{
|
|
/* Place it here. */
|
|
lit->header.next = *s_p;
|
|
*s_p = lit;
|
|
placed = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!placed)
|
|
{
|
|
/* Put it at the end. */
|
|
*w->children.tail = lit;
|
|
w->children.tail = &lit->header.next;
|
|
}
|
|
}
|
|
|
|
#if EXTRA_VALIDATION
|
|
new_count = ld_count_children ((lang_statement_union_type*) w);
|
|
ASSERT (new_count == old_count);
|
|
#endif
|
|
}
|
|
|
|
|
|
static void
|
|
xtensa_colocate_output_literals_callback (lang_statement_union_type *statement)
|
|
{
|
|
reloc_deps_graph *deps;
|
|
if (statement->header.type == lang_output_section_statement_enum)
|
|
{
|
|
/* Now, we walk over the contours of the output section statement.
|
|
|
|
First we build the literal section dependences as before.
|
|
|
|
At the first uniquely_literal section, we mark it as a good
|
|
spot to place other literals. Continue walking (and counting
|
|
sizes) until we find the next literal section. If this
|
|
section can be moved to the first one, then we move it. If
|
|
we every find a modification of ".", start over. If we find
|
|
a labeling of the current location, start over. Finally, at
|
|
the end, if we require page alignment, add page alignments. */
|
|
|
|
#if EXTRA_VALIDATION
|
|
size_t old_child_count;
|
|
size_t new_child_count;
|
|
#endif
|
|
bfd_boolean no_reorder = FALSE;
|
|
|
|
#if EXTRA_VALIDATION
|
|
old_child_count = ld_count_children (statement);
|
|
#endif
|
|
|
|
/* It is now officially a target. Build the graph of source
|
|
section -> target section (kept as a list of edges). */
|
|
|
|
deps = ld_build_required_section_dependence (statement);
|
|
|
|
/* If this wildcard does not reorder.... */
|
|
if (!no_reorder)
|
|
{
|
|
/* First check for reverse dependences. Fix if possible. */
|
|
xtensa_colocate_literals (deps, statement);
|
|
|
|
#if EXTRA_VALIDATION
|
|
new_child_count = ld_count_children (statement);
|
|
ASSERT (new_child_count == old_child_count);
|
|
#endif
|
|
}
|
|
|
|
/* Insert align/offset assignment statement. */
|
|
if (xtensa_use_literal_pages)
|
|
{
|
|
ld_xtensa_insert_page_offsets (0, statement, deps,
|
|
xtensa_use_literal_pages);
|
|
lang_for_each_statement_worker (xtensa_ldlang_clear_addresses,
|
|
statement);
|
|
}
|
|
|
|
/* Clean up. */
|
|
free_reloc_deps_graph (deps);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
xtensa_colocate_output_literals (lang_statement_union_type *s)
|
|
{
|
|
lang_for_each_statement_worker (xtensa_colocate_output_literals_callback, s);
|
|
}
|
|
|
|
|
|
static void
|
|
xtensa_ldlang_clear_addresses (lang_statement_union_type *statement)
|
|
{
|
|
switch (statement->header.type)
|
|
{
|
|
case lang_input_section_enum:
|
|
{
|
|
asection *bfd_section = statement->input_section.section;
|
|
bfd_section->output_offset = 0;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static bfd_vma
|
|
ld_assign_relative_paged_dot (bfd_vma dot,
|
|
lang_statement_union_type *s,
|
|
const reloc_deps_graph *deps ATTRIBUTE_UNUSED,
|
|
bfd_boolean lit_align)
|
|
{
|
|
/* Walk through all of the input statements in this wild statement
|
|
assign dot to all of them. */
|
|
|
|
xtensa_ld_iter_stack *stack = NULL;
|
|
xtensa_ld_iter_stack **stack_p = &stack;
|
|
|
|
bfd_boolean first_section = FALSE;
|
|
bfd_boolean in_literals = FALSE;
|
|
|
|
for (iter_stack_create (stack_p, s);
|
|
!iter_stack_empty (stack_p);
|
|
iter_stack_next (stack_p))
|
|
{
|
|
lang_statement_union_type *l = iter_stack_current (stack_p);
|
|
|
|
switch (l->header.type)
|
|
{
|
|
case lang_input_section_enum:
|
|
{
|
|
asection *section = l->input_section.section;
|
|
size_t align_pow = section->alignment_power;
|
|
bfd_boolean do_xtensa_alignment = FALSE;
|
|
|
|
if (lit_align)
|
|
{
|
|
bfd_boolean sec_is_target = section_is_target (deps, l);
|
|
bfd_boolean sec_is_source = section_is_source (deps, l);
|
|
|
|
if (section->size != 0
|
|
&& (first_section
|
|
|| (in_literals && !sec_is_target)
|
|
|| (!in_literals && sec_is_target)))
|
|
{
|
|
do_xtensa_alignment = TRUE;
|
|
}
|
|
first_section = FALSE;
|
|
if (section->size != 0)
|
|
in_literals = (sec_is_target && !sec_is_source);
|
|
}
|
|
|
|
if (do_xtensa_alignment && xtensa_page_power != 0)
|
|
dot += (1 << xtensa_page_power);
|
|
|
|
dot = align_power (dot, align_pow);
|
|
section->output_offset = dot;
|
|
dot += section->size;
|
|
}
|
|
break;
|
|
case lang_fill_statement_enum:
|
|
dot += l->fill_statement.size;
|
|
break;
|
|
case lang_padding_statement_enum:
|
|
dot += l->padding_statement.size;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return dot;
|
|
}
|
|
|
|
|
|
static bfd_boolean
|
|
ld_local_file_relocations_fit (lang_statement_union_type *statement,
|
|
const reloc_deps_graph *deps ATTRIBUTE_UNUSED)
|
|
{
|
|
/* Walk over all of the dependencies that we identified and make
|
|
sure that IF the source and target are here (addr != 0):
|
|
1) target addr < source addr
|
|
2) (roundup(source + source_size, 4) - rounddown(target, 4))
|
|
< (256K - (1 << bad align))
|
|
Need a worst-case proof.... */
|
|
|
|
xtensa_ld_iter_stack *stack = NULL;
|
|
xtensa_ld_iter_stack **stack_p = &stack;
|
|
size_t max_align_power = 0;
|
|
size_t align_penalty = 256;
|
|
reloc_deps_e *e;
|
|
size_t i;
|
|
|
|
/* Find the worst-case alignment requirement for this set of statements. */
|
|
for (iter_stack_create (stack_p, statement);
|
|
!iter_stack_empty (stack_p);
|
|
iter_stack_next (stack_p))
|
|
{
|
|
lang_statement_union_type *l = iter_stack_current (stack_p);
|
|
if (l->header.type == lang_input_section_enum)
|
|
{
|
|
lang_input_section_type *input = &l->input_section;
|
|
asection *section = input->section;
|
|
if (section->alignment_power > max_align_power)
|
|
max_align_power = section->alignment_power;
|
|
}
|
|
}
|
|
|
|
/* Now check that everything fits. */
|
|
for (i = 0; i < deps->count; i++)
|
|
{
|
|
asection *sec = deps->sections[i];
|
|
const reloc_deps_section *deps_section =
|
|
xtensa_get_section_deps (deps, sec);
|
|
if (deps_section)
|
|
{
|
|
/* We choose to walk through the successors. */
|
|
for (e = deps_section->succs; e != NULL; e = e->next)
|
|
{
|
|
if (e->src != e->tgt
|
|
&& e->src->output_section == e->tgt->output_section
|
|
&& e->src->output_offset != 0
|
|
&& e->tgt->output_offset != 0)
|
|
{
|
|
bfd_vma l32r_addr =
|
|
align_power (e->src->output_offset + e->src->size, 2);
|
|
bfd_vma target_addr = e->tgt->output_offset & ~3;
|
|
if (l32r_addr < target_addr)
|
|
{
|
|
fflush (stdout);
|
|
fprintf (stderr, "Warning: "
|
|
"l32r target section before l32r\n");
|
|
fflush (stderr);
|
|
return FALSE;
|
|
}
|
|
|
|
if (l32r_addr - target_addr > 256 * 1024 - align_penalty)
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static bfd_vma
|
|
ld_xtensa_insert_page_offsets (bfd_vma dot,
|
|
lang_statement_union_type *s,
|
|
reloc_deps_graph *deps,
|
|
bfd_boolean lit_align)
|
|
{
|
|
xtensa_ld_iter_stack *stack = NULL;
|
|
xtensa_ld_iter_stack **stack_p = &stack;
|
|
|
|
bfd_boolean first_section = FALSE;
|
|
bfd_boolean in_literals = FALSE;
|
|
|
|
if (!lit_align)
|
|
return FALSE;
|
|
|
|
for (iter_stack_create (stack_p, s);
|
|
!iter_stack_empty (stack_p);
|
|
iter_stack_next (stack_p))
|
|
{
|
|
lang_statement_union_type *l = iter_stack_current (stack_p);
|
|
|
|
switch (l->header.type)
|
|
{
|
|
case lang_input_section_enum:
|
|
{
|
|
asection *section = l->input_section.section;
|
|
bfd_boolean do_xtensa_alignment = FALSE;
|
|
|
|
if (lit_align)
|
|
{
|
|
if (section->size != 0
|
|
&& (first_section
|
|
|| (in_literals && !section_is_target (deps, l))
|
|
|| (!in_literals && section_is_target (deps, l))))
|
|
{
|
|
do_xtensa_alignment = TRUE;
|
|
}
|
|
first_section = FALSE;
|
|
if (section->size != 0)
|
|
{
|
|
in_literals = (section_is_target (deps, l)
|
|
&& !section_is_source (deps, l));
|
|
}
|
|
}
|
|
|
|
if (do_xtensa_alignment && xtensa_page_power != 0)
|
|
{
|
|
/* Create an expression that increments the current address,
|
|
i.e., "dot", by (1 << xtensa_align_power). */
|
|
etree_type *name_op = exp_nameop (NAME, ".");
|
|
etree_type *addend_op = exp_intop (1 << xtensa_page_power);
|
|
etree_type *add_op = exp_binop ('+', name_op, addend_op);
|
|
etree_type *assign_op = exp_assign (".", add_op, FALSE);
|
|
|
|
lang_assignment_statement_type *assign_stmt;
|
|
lang_statement_union_type *assign_union;
|
|
lang_statement_list_type tmplist;
|
|
|
|
/* There is hidden state in "lang_add_assignment". It
|
|
appends the new assignment statement to the stat_ptr
|
|
list. Thus, we swap it before and after the call. */
|
|
|
|
lang_list_init (&tmplist);
|
|
push_stat_ptr (&tmplist);
|
|
/* Warning: side effect; statement appended to stat_ptr. */
|
|
assign_stmt = lang_add_assignment (assign_op);
|
|
assign_union = (lang_statement_union_type *) assign_stmt;
|
|
pop_stat_ptr ();
|
|
|
|
assign_union->header.next = l;
|
|
*(*stack_p)->iterloc.loc = assign_union;
|
|
iter_stack_next (stack_p);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return dot;
|
|
}
|
|
|
|
EOF
|
|
|
|
# Define some shell vars to insert bits of code into the standard ELF
|
|
# parse_args and list_options functions.
|
|
#
|
|
PARSE_AND_LIST_PROLOGUE='
|
|
#define OPTION_OPT_SIZEOPT (300)
|
|
#define OPTION_LITERAL_MOVEMENT (OPTION_OPT_SIZEOPT + 1)
|
|
#define OPTION_NO_LITERAL_MOVEMENT (OPTION_LITERAL_MOVEMENT + 1)
|
|
extern int elf32xtensa_size_opt;
|
|
extern int elf32xtensa_no_literal_movement;
|
|
'
|
|
|
|
PARSE_AND_LIST_LONGOPTS='
|
|
{ "size-opt", no_argument, NULL, OPTION_OPT_SIZEOPT},
|
|
{ "literal-movement", no_argument, NULL, OPTION_LITERAL_MOVEMENT},
|
|
{ "no-literal-movement", no_argument, NULL, OPTION_NO_LITERAL_MOVEMENT},
|
|
'
|
|
|
|
PARSE_AND_LIST_OPTIONS='
|
|
fprintf (file, _("\
|
|
--size-opt When relaxing longcalls, prefer size\n\
|
|
optimization over branch target alignment\n"));
|
|
'
|
|
|
|
PARSE_AND_LIST_ARGS_CASES='
|
|
case OPTION_OPT_SIZEOPT:
|
|
elf32xtensa_size_opt = 1;
|
|
break;
|
|
case OPTION_LITERAL_MOVEMENT:
|
|
elf32xtensa_no_literal_movement = 0;
|
|
break;
|
|
case OPTION_NO_LITERAL_MOVEMENT:
|
|
elf32xtensa_no_literal_movement = 1;
|
|
break;
|
|
'
|
|
|
|
# Replace some of the standard ELF functions with our own versions.
|
|
#
|
|
LDEMUL_BEFORE_PARSE=elf_xtensa_before_parse
|
|
LDEMUL_AFTER_OPEN=elf_xtensa_after_open
|
|
LDEMUL_CHOOSE_TARGET=elf_xtensa_choose_target
|
|
LDEMUL_BEFORE_ALLOCATION=elf_xtensa_before_allocation
|