Rework the R_NEG support on both gas and ld for the PowerPC AIX targets, in order...
[binutils-gdb.git] / gas / config / obj-elf.c
index a6dcdaf4a7e4c21f73ec659f72a3bffccf53e90a..9f85c576462b98947dc13fcea0df72828e4ae1e5 100644 (file)
@@ -1,5 +1,5 @@
 /* ELF object file format
-   Copyright (C) 1992-2020 Free Software Foundation, Inc.
+   Copyright (C) 1992-2021 Free Software Foundation, Inc.
 
    This file is part of GAS, the GNU Assembler.
 
@@ -78,9 +78,11 @@ static void obj_elf_gnu_attribute (int);
 static void obj_elf_tls_common (int);
 static void obj_elf_lcomm (int);
 static void obj_elf_struct (int);
+static void obj_elf_attach_to_group (int);
 
 static const pseudo_typeS elf_pseudo_table[] =
 {
+  {"attach_to_group", obj_elf_attach_to_group, 0},
   {"comm", obj_elf_common, 0},
   {"common", obj_elf_common, 1},
   {"ident", obj_elf_ident, 0},
@@ -119,10 +121,6 @@ static const pseudo_typeS elf_pseudo_table[] =
   /* A GNU extension for object attributes.  */
   {"gnu_attribute", obj_elf_gnu_attribute, 0},
 
-  /* These are used for dwarf.  */
-  {"2byte", cons, 2},
-  {"4byte", cons, 4},
-  {"8byte", cons, 8},
   /* These are used for dwarf2.  */
   { "file", dwarf2_directive_file, 0 },
   { "loc",  dwarf2_directive_loc,  0 },
@@ -271,8 +269,7 @@ elf_file_symbol (const char *s, int appfile)
       symbolS *sym;
       size_t name_length;
 
-      sym = symbol_new (s, absolute_section, 0, NULL);
-      symbol_set_frag (sym, &zero_address_frag);
+      sym = symbol_new (s, absolute_section, &zero_address_frag, 0);
 
       name_length = strlen (s);
       if (name_length > strlen (S_GET_NAME (sym)))
@@ -327,9 +324,9 @@ elf_common_parse (int ignore ATTRIBUTE_UNUSED, symbolS *symbolP, addressT size)
          if (*input_line_pointer == '.')
            input_line_pointer++;
          /* Some say data, some say bss.  */
-         if (strncmp (input_line_pointer, "bss\"", 4) == 0)
+         if (startswith (input_line_pointer, "bss\""))
            input_line_pointer += 4;
-         else if (strncmp (input_line_pointer, "data\"", 5) == 0)
+         else if (startswith (input_line_pointer, "data\""))
            input_line_pointer += 5;
          else
            {
@@ -483,7 +480,7 @@ obj_elf_visibility (int visibility)
       symbolP = get_sym_from_input_line_and_check ();
 
       bfdsym = symbol_get_bfdsym (symbolP);
-      elfsym = elf_symbol_from (bfd_asymbol_bfd (bfdsym), bfdsym);
+      elfsym = elf_symbol_from (bfdsym);
 
       gas_assert (elfsym);
 
@@ -518,17 +515,25 @@ struct section_stack
 
 static struct section_stack *section_stack;
 
-static bfd_boolean
-get_section (bfd *abfd ATTRIBUTE_UNUSED, asection *sec, void *inf)
+/* ELF section flags for unique sections.  */
+#define SEC_ASSEMBLER_SHF_MASK SHF_GNU_RETAIN
+
+/* Return TRUE iff SEC matches the section info INF.  */
+
+static bool
+get_section_by_match (bfd *abfd ATTRIBUTE_UNUSED, asection *sec, void *inf)
 {
   struct elf_section_match *match = (struct elf_section_match *) inf;
   const char *gname = match->group_name;
   const char *group_name = elf_group_name (sec);
   const char *linked_to_symbol_name
     = sec->map_head.linked_to_symbol_name;
-  unsigned int info = elf_section_data (sec)->this_hdr.sh_info;
+  unsigned int sh_info = elf_section_data (sec)->this_hdr.sh_info;
+  bfd_vma sh_flags = (elf_section_data (sec)->this_hdr.sh_flags
+                     & SEC_ASSEMBLER_SHF_MASK);
 
-  return (info == match->info
+  return (sh_info == match->sh_info
+         && sh_flags == match->sh_flags
          && ((bfd_section_flags (sec) & SEC_ASSEMBLER_SECTION_ID)
               == (match->flags & SEC_ASSEMBLER_SECTION_ID))
          && sec->section_id == match->section_id
@@ -601,7 +606,7 @@ obj_elf_change_section (const char *name,
   previous_section = now_seg;
   previous_subsection = now_subseg;
 
-  old_sec = bfd_get_section_by_name_if (stdoutput, name, get_section,
+  old_sec = bfd_get_section_by_name_if (stdoutput, name, get_section_by_match,
                                        (void *) match_p);
   if (old_sec)
     {
@@ -616,7 +621,7 @@ obj_elf_change_section (const char *name,
 
   if (ssect != NULL)
     {
-      bfd_boolean override = FALSE;
+      bool override = false;
 
       if (type == SHT_NULL)
        type = ssect->type;
@@ -657,42 +662,50 @@ obj_elf_change_section (const char *name,
            }
        }
 
-      if (old_sec == NULL && ((attr & ~(SHF_MASKOS | SHF_MASKPROC))
+      if (old_sec == NULL && ((attr & ~(SHF_LINK_ORDER
+                                       | SHF_MASKOS
+                                       | SHF_MASKPROC))
                              & ~ssect->attr) != 0)
        {
+         /* Strip SHF_GNU_RETAIN.  */
+         bfd_vma generic_attr = attr;
+         if (elf_tdata (stdoutput)->has_gnu_osabi)
+           generic_attr &= ~SHF_GNU_RETAIN;
+
          /* As a GNU extension, we permit a .note section to be
             allocatable.  If the linker sees an allocatable .note
             section, it will create a PT_NOTE segment in the output
             file.  We also allow "x" for .note.GNU-stack.  */
          if (ssect->type == SHT_NOTE
-             && (attr == SHF_ALLOC || attr == SHF_EXECINSTR))
+             && (generic_attr == SHF_ALLOC
+                 || generic_attr == SHF_EXECINSTR))
            ;
          /* Allow different SHF_MERGE and SHF_STRINGS if we have
             something like .rodata.str.  */
          else if (ssect->suffix_length == -2
                   && name[ssect->prefix_length] == '.'
-                  && (attr
+                  && (generic_attr
                       & ~ssect->attr
                       & ~SHF_MERGE
                       & ~SHF_STRINGS) == 0)
            ;
          /* .interp, .strtab and .symtab can have SHF_ALLOC.  */
-         else if (attr == SHF_ALLOC
+         else if (generic_attr == SHF_ALLOC
                   && (strcmp (name, ".interp") == 0
                       || strcmp (name, ".strtab") == 0
                       || strcmp (name, ".symtab") == 0))
-           override = TRUE;
+           override = true;
          /* .note.GNU-stack can have SHF_EXECINSTR.  */
-         else if (attr == SHF_EXECINSTR
+         else if (generic_attr == SHF_EXECINSTR
                   && strcmp (name, ".note.GNU-stack") == 0)
-           override = TRUE;
+           override = true;
 #ifdef TC_ALPHA
          /* A section on Alpha may have SHF_ALPHA_GPREL.  */
-         else if ((attr & ~ssect->attr) == SHF_ALPHA_GPREL)
-           override = TRUE;
+         else if ((generic_attr & ~ssect->attr) == SHF_ALPHA_GPREL)
+           override = true;
 #endif
 #ifdef TC_RX
-         else if (attr == (SHF_EXECINSTR | SHF_WRITE | SHF_ALLOC)
+         else if (generic_attr == (SHF_EXECINSTR | SHF_WRITE | SHF_ALLOC)
                   && (ssect->type == SHT_INIT_ARRAY
                       || ssect->type == SHT_FINI_ARRAY
                       || ssect->type == SHT_PREINIT_ARRAY))
@@ -704,7 +717,7 @@ obj_elf_change_section (const char *name,
              if (match_p->group_name == NULL)
                as_warn (_("setting incorrect section attributes for %s"),
                         name);
-             override = TRUE;
+             override = true;
            }
        }
 
@@ -737,7 +750,7 @@ obj_elf_change_section (const char *name,
        type = bfd_elf_get_default_section_type (flags);
       elf_section_type (sec) = type;
       elf_section_flags (sec) = attr;
-      elf_section_data (sec)->this_hdr.sh_info = match_p->info;
+      elf_section_data (sec)->this_hdr.sh_info = match_p->sh_info;
 
       /* Prevent SEC_HAS_CONTENTS from being inadvertently set.  */
       if (type == SHT_NOBITS)
@@ -759,7 +772,14 @@ obj_elf_change_section (const char *name,
       /* Add a symbol for this section to the symbol table.  */
       secsym = symbol_find (name);
       if (secsym != NULL)
-       symbol_set_bfdsym (secsym, sec->symbol);
+       {
+         /* We could be repurposing an undefined symbol here: make sure we
+            reset sy_value to look like other section symbols in order to avoid
+            trying to incorrectly resolve this section symbol later on.  */
+         static const expressionS exp = { .X_op = O_constant };
+         symbol_set_value_expression (secsym, &exp);
+         symbol_set_bfdsym (secsym, sec->symbol);
+       }
       else
        symbol_table_insert (section_symbol (sec));
     }
@@ -797,7 +817,7 @@ obj_elf_change_section (const char *name,
            }
          else
            /* FIXME: Maybe we should consider removing a previously set
-              processor or application specific attribute as suspicious ?  */
+              processor or application specific attribute as suspicious?  */
            elf_section_flags (sec) = attr;
 
          if ((flags & SEC_MERGE) && old_sec->entsize != (unsigned) entsize)
@@ -812,10 +832,10 @@ obj_elf_change_section (const char *name,
 
 static bfd_vma
 obj_elf_parse_section_letters (char *str, size_t len,
-                              bfd_boolean *is_clone, bfd_vma *gnu_attr)
+                              bool *is_clone, bfd_vma *gnu_attr)
 {
   bfd_vma attr = 0;
-  *is_clone = FALSE;
+  *is_clone = false;
 
   while (len > 0)
     {
@@ -851,8 +871,11 @@ obj_elf_parse_section_letters (char *str, size_t len,
        case 'd':
          *gnu_attr |= SHF_GNU_MBIND;
          break;
+       case 'R':
+         *gnu_attr |= SHF_GNU_RETAIN;
+         break;
        case '?':
-         *is_clone = TRUE;
+         *is_clone = true;
          break;
        /* Compatibility.  */
        case 'm':
@@ -880,8 +903,32 @@ obj_elf_parse_section_letters (char *str, size_t len,
              if (ISDIGIT (*str))
                {
                  char * end;
+                 struct elf_backend_data *bed;
+                 bfd_vma numeric_flags = strtoul (str, &end, 0);
+
+                 attr |= numeric_flags;
+
+                 bed = (struct elf_backend_data *)
+                   get_elf_backend_data (stdoutput);
+
+                 if (bed->elf_osabi == ELFOSABI_NONE
+                     || bed->elf_osabi == ELFOSABI_STANDALONE
+                     || bed->elf_osabi == ELFOSABI_GNU
+                     || bed->elf_osabi == ELFOSABI_FREEBSD)
+                   {
+                     /* Add flags in the SHF_MASKOS range to gnu_attr for
+                        OSABIs that support those flags.
+                        Also adding the flags for ELFOSABI_{NONE,STANDALONE}
+                        allows them to be validated later in obj_elf_section.
+                        We can't just always set these bits in gnu_attr for
+                        all OSABIs, since Binutils does not recognize all
+                        SHF_MASKOS bits for non-GNU OSABIs.  It's therefore
+                        possible that numeric flags are being used to set bits
+                        in the SHF_MASKOS range for those targets, and we
+                        don't want assembly to fail in those situations.  */
+                     *gnu_attr |= (numeric_flags & SHF_MASKOS);
+                   }
 
-                 attr |= strtoul (str, & end, 0);
                  /* Update str and len, allowing for the fact that
                     we will execute str++ and len-- below.  */
                  end --;
@@ -900,19 +947,19 @@ obj_elf_parse_section_letters (char *str, size_t len,
 }
 
 static int
-obj_elf_section_type (char *str, size_t len, bfd_boolean warn)
+obj_elf_section_type (char *str, size_t len, bool warn)
 {
-  if (len == 8 && strncmp (str, "progbits", 8) == 0)
+  if (len == 8 && startswith (str, "progbits"))
     return SHT_PROGBITS;
-  if (len == 6 && strncmp (str, "nobits", 6) == 0)
+  if (len == 6 && startswith (str, "nobits"))
     return SHT_NOBITS;
-  if (len == 4 && strncmp (str, "note", 4) == 0)
+  if (len == 4 && startswith (str, "note"))
     return SHT_NOTE;
-  if (len == 10 && strncmp (str, "init_array", 10) == 0)
+  if (len == 10 && startswith (str, "init_array"))
     return SHT_INIT_ARRAY;
-  if (len == 10 && strncmp (str, "fini_array", 10) == 0)
+  if (len == 10 && startswith (str, "fini_array"))
     return SHT_FINI_ARRAY;
-  if (len == 13 && strncmp (str, "preinit_array", 13) == 0)
+  if (len == 13 && startswith (str, "preinit_array"))
     return SHT_PREINIT_ARRAY;
 
 #ifdef md_elf_section_type
@@ -944,15 +991,15 @@ obj_elf_section_word (char *str, size_t len, int *type)
 {
   int ret;
 
-  if (len == 5 && strncmp (str, "write", 5) == 0)
+  if (len == 5 && startswith (str, "write"))
     return SHF_WRITE;
-  if (len == 5 && strncmp (str, "alloc", 5) == 0)
+  if (len == 5 && startswith (str, "alloc"))
     return SHF_ALLOC;
-  if (len == 9 && strncmp (str, "execinstr", 9) == 0)
+  if (len == 9 && startswith (str, "execinstr"))
     return SHF_EXECINSTR;
-  if (len == 7 && strncmp (str, "exclude", 7) == 0)
+  if (len == 7 && startswith (str, "exclude"))
     return SHF_EXCLUDE;
-  if (len == 3 && strncmp (str, "tls", 3) == 0)
+  if (len == 3 && startswith (str, "tls"))
     return SHF_TLS;
 
 #ifdef md_elf_section_word
@@ -963,7 +1010,7 @@ obj_elf_section_word (char *str, size_t len, int *type)
   }
 #endif
 
-  ret = obj_elf_section_type (str, len, FALSE);
+  ret = obj_elf_section_type (str, len, false);
   if (ret != 0)
     *type = ret;
   else
@@ -1034,6 +1081,28 @@ obj_elf_section_name (void)
   return name;
 }
 
+static void
+obj_elf_attach_to_group (int dummy ATTRIBUTE_UNUSED)
+{
+  const char * gname = obj_elf_section_name ();
+
+  if (gname == NULL)
+    {
+      as_warn (_("group name not parseable"));
+      return;
+    }
+
+  if (elf_group_name (now_seg))
+    {
+      as_warn (_("section %s already has a group (%s)"),
+              bfd_section_name (now_seg), elf_group_name (now_seg));
+      return;
+    }
+
+  elf_group_name (now_seg) = xstrdup (gname);
+  elf_section_flags (now_seg) |= SHF_GROUP;
+}
+
 void
 obj_elf_section (int push)
 {
@@ -1046,6 +1115,7 @@ obj_elf_section (int push)
   int linkonce;
   subsegT new_subsection = -1;
   struct elf_section_match match;
+  unsigned long linked_to_section_index = -1UL;
 
   if (flag_mri)
     {
@@ -1114,7 +1184,7 @@ obj_elf_section (int push)
 
       if (*input_line_pointer == '"')
        {
-         bfd_boolean is_clone;
+         bool is_clone;
 
          beg = demand_copy_C_string (&dummy);
          if (beg == NULL)
@@ -1142,7 +1212,7 @@ obj_elf_section (int push)
                      ignore_rest_of_line ();
                      return;
                    }
-                 type = obj_elf_section_type (beg, strlen (beg), TRUE);
+                 type = obj_elf_section_type (beg, strlen (beg), true);
                }
              else if (c == '@' || c == '%')
                {
@@ -1156,7 +1226,7 @@ obj_elf_section (int push)
                      (void) restore_line_pointer (c);
                      type = obj_elf_section_type (beg,
                                                   input_line_pointer - beg,
-                                                  TRUE);
+                                                  true);
                    }
                }
              else
@@ -1185,22 +1255,32 @@ obj_elf_section (int push)
 
          if ((attr & SHF_LINK_ORDER) != 0 && *input_line_pointer == ',')
            {
-             char c;
-             unsigned int length;
              ++input_line_pointer;
              SKIP_WHITESPACE ();
-             c = get_symbol_name (& beg);
-             (void) restore_line_pointer (c);
-             length = input_line_pointer - beg;
-             if (length)
-               match.linked_to_symbol_name = xmemdup0 (beg, length);
+             /* Check for a numeric section index, rather than a symbol name.  */
+             if (ISDIGIT (* input_line_pointer))
+               {
+                 linked_to_section_index = strtoul (input_line_pointer, & input_line_pointer, 0);
+               }
+             else
+               {
+                 char c;
+                 unsigned int length;
+
+                 c = get_symbol_name (& beg);
+                 (void) restore_line_pointer (c);
+                 length = input_line_pointer - beg;
+                 if (length)
+                   match.linked_to_symbol_name = xmemdup0 (beg, length);
+               }
            }
 
          if ((attr & SHF_GROUP) != 0 && is_clone)
            {
              as_warn (_("? section flag ignored with G present"));
-             is_clone = FALSE;
+             is_clone = false;
            }
+
          if ((attr & SHF_GROUP) != 0 && *input_line_pointer == ',')
            {
              ++input_line_pointer;
@@ -1211,13 +1291,13 @@ obj_elf_section (int push)
                {
                  ++input_line_pointer;
                  SKIP_WHITESPACE ();
-                 if (strncmp (input_line_pointer, "comdat", 6) == 0)
+                 if (startswith (input_line_pointer, "comdat"))
                    {
                      input_line_pointer += 6;
                      linkonce = 1;
                    }
                }
-             else if (strncmp (name, ".gnu.linkonce", 13) == 0)
+             else if (startswith (name, ".gnu.linkonce"))
                linkonce = 1;
            }
          else if ((attr & SHF_GROUP) != 0)
@@ -1244,24 +1324,28 @@ obj_elf_section (int push)
              if (ISDIGIT (* input_line_pointer))
                {
                  char *t = input_line_pointer;
-                 match.info = strtoul (input_line_pointer,
+                 match.sh_info = strtoul (input_line_pointer,
                                        &input_line_pointer, 0);
-                 if (match.info == (unsigned int) -1)
+                 if (match.sh_info == (unsigned int) -1)
                    {
                      as_warn (_("unsupported mbind section info: %s"), t);
-                     match.info = 0;
+                     match.sh_info = 0;
                    }
                }
              else
                input_line_pointer = save;
            }
 
+         if ((gnu_attr & SHF_GNU_RETAIN) != 0)
+           match.sh_flags |= SHF_GNU_RETAIN;
+
          if (*input_line_pointer == ',')
            {
              char *save = input_line_pointer;
+
              ++input_line_pointer;
              SKIP_WHITESPACE ();
-             if (strncmp (input_line_pointer, "unique", 6) == 0)
+             if (startswith (input_line_pointer, "unique"))
                {
                  input_line_pointer += 6;
                  SKIP_WHITESPACE ();
@@ -1272,7 +1356,7 @@ obj_elf_section (int push)
                      if (ISDIGIT (* input_line_pointer))
                        {
                          bfd_vma id;
-                         bfd_boolean overflow;
+                         bool overflow;
                          char *t = input_line_pointer;
                          if (sizeof (bfd_vma) <= sizeof (unsigned long))
                            {
@@ -1343,26 +1427,41 @@ obj_elf_section (int push)
  done:
   demand_empty_rest_of_line ();
 
-  obj_elf_change_section (name, type, attr, entsize, &match, linkonce,
-                         push);
-
-  if ((gnu_attr & SHF_GNU_MBIND) != 0)
+  if ((gnu_attr & (SHF_GNU_MBIND | SHF_GNU_RETAIN)) != 0)
     {
-      struct elf_backend_data *bed;
+      const struct elf_backend_data *bed;
+      bool mbind_p = (gnu_attr & SHF_GNU_MBIND) != 0;
 
-      if ((attr & SHF_ALLOC) == 0)
+      if (mbind_p && (attr & SHF_ALLOC) == 0)
        as_bad (_("SHF_ALLOC isn't set for GNU_MBIND section: %s"), name);
 
-      bed = (struct elf_backend_data *) get_elf_backend_data (stdoutput);
-      if (bed->elf_osabi == ELFOSABI_NONE)
-       bed->elf_osabi = ELFOSABI_GNU;
-      else if (bed->elf_osabi != ELFOSABI_GNU
-              && bed->elf_osabi != ELFOSABI_FREEBSD)
-       as_bad (_("GNU_MBIND section is supported only by GNU "
-                 "and FreeBSD targets"));
-      elf_tdata (stdoutput)->has_gnu_osabi |= elf_gnu_osabi_mbind;
+      bed = get_elf_backend_data (stdoutput);
+
+      if (bed->elf_osabi != ELFOSABI_GNU
+         && bed->elf_osabi != ELFOSABI_FREEBSD
+         && bed->elf_osabi != ELFOSABI_NONE)
+       as_bad (_("%s section is supported only by GNU and FreeBSD targets"),
+               mbind_p ? "GNU_MBIND" : "GNU_RETAIN");
+      else
+       {
+         if (mbind_p)
+           elf_tdata (stdoutput)->has_gnu_osabi |= elf_gnu_osabi_mbind;
+         if ((gnu_attr & SHF_GNU_RETAIN) != 0)
+           elf_tdata (stdoutput)->has_gnu_osabi |= elf_gnu_osabi_retain;
+
+         attr |= gnu_attr;
+       }
+    }
+
+  obj_elf_change_section (name, type, attr, entsize, &match, linkonce,
+                         push);
+
+  if (linked_to_section_index != -1UL)
+    {
+      elf_section_flags (now_seg) |= SHF_LINK_ORDER;
+      elf_section_data (now_seg)->this_hdr.sh_link = linked_to_section_index;
+      /* FIXME: Should we perform some sanity checking on the section index ?  */
     }
-  elf_section_flags (now_seg) |= gnu_attr;
 
   if (push && new_subsection != -1)
     subseg_set (now_seg, new_subsection);
@@ -1515,6 +1614,70 @@ obj_elf_line (int ignore ATTRIBUTE_UNUSED)
   demand_empty_rest_of_line ();
 }
 
+static struct elf_versioned_name_list *
+obj_elf_find_and_add_versioned_name (const char *version_name,
+                                    const char *sym_name,
+                                    const char *ver,
+                                    struct elf_obj_sy *sy_obj)
+{
+  struct elf_versioned_name_list *versioned_name;
+  const char *p;
+
+  for (p = ver + 1; *p == ELF_VER_CHR; p++)
+    ;
+
+  /* NB: Since some tests in ld/testsuite/ld-elfvers have no version
+     names, we have to disable this.  */
+  if (0 && *p == '\0')
+    {
+      as_bad (_("missing version name in `%s' for symbol `%s'"),
+             version_name, sym_name);
+      return NULL;
+    }
+
+  versioned_name = sy_obj->versioned_name;
+
+  switch (p - ver)
+    {
+    case 1:
+    case 2:
+      break;
+    case 3:
+      if (sy_obj->rename)
+       {
+         if (strcmp (versioned_name->name, version_name) == 0)
+           return versioned_name;
+         else
+           {
+             as_bad (_("only one version name with `@@@' is allowed "
+                       "for symbol `%s'"), sym_name);
+             return NULL;
+           }
+       }
+      sy_obj->rename = true;
+      break;
+    default:
+      as_bad (_("invalid version name '%s' for symbol `%s'"),
+             version_name, sym_name);
+      return NULL;
+    }
+
+  for (;
+       versioned_name != NULL;
+       versioned_name = versioned_name->next)
+    if (strcmp (versioned_name->name, version_name) == 0)
+      return versioned_name;
+
+  /* Add this versioned name to the head of the list,  */
+  versioned_name = (struct elf_versioned_name_list *)
+    xmalloc (sizeof (*versioned_name));
+  versioned_name->name = xstrdup (version_name);
+  versioned_name->next = sy_obj->versioned_name;
+  sy_obj->versioned_name = versioned_name;
+
+  return versioned_name;
+}
+
 /* This handles the .symver pseudo-op, which is used to specify a
    symbol version.  The syntax is ``.symver NAME,SYMVERNAME''.
    SYMVERNAME may contain ELF_VER_CHR ('@') characters.  This
@@ -1525,9 +1688,12 @@ static void
 obj_elf_symver (int ignore ATTRIBUTE_UNUSED)
 {
   char *name;
+  const char *sym_name;
   char c;
   char old_lexat;
   symbolS *sym;
+  struct elf_obj_sy *sy_obj;
+  char *p;
 
   sym = get_sym_from_input_line_and_check ();
 
@@ -1546,43 +1712,59 @@ obj_elf_symver (int ignore ATTRIBUTE_UNUSED)
   lex_type[(unsigned char) '@'] |= LEX_NAME;
   c = get_symbol_name (& name);
   lex_type[(unsigned char) '@'] = old_lexat;
+  sym_name = S_GET_NAME (sym);
 
   if (S_IS_COMMON (sym))
     {
       as_bad (_("`%s' can't be versioned to common symbol '%s'"),
-             name, S_GET_NAME (sym));
+             name, sym_name);
+      ignore_rest_of_line ();
+      return;
+    }
+
+  p = strchr (name, ELF_VER_CHR);
+  if (p == NULL)
+    {
+      as_bad (_("missing version name in `%s' for symbol `%s'"),
+             name, sym_name);
       ignore_rest_of_line ();
       return;
     }
 
-  if (symbol_get_obj (sym)->versioned_name == NULL)
+  sy_obj = symbol_get_obj (sym);
+  if (obj_elf_find_and_add_versioned_name (name, sym_name,
+                                          p, sy_obj) == NULL)
     {
-      symbol_get_obj (sym)->versioned_name = xstrdup (name);
+      sy_obj->bad_version = true;
+      ignore_rest_of_line ();
+      return;
+    }
+
+  (void) restore_line_pointer (c);
 
-      (void) restore_line_pointer (c);
+  if (*input_line_pointer == ',')
+    {
+      char *save = input_line_pointer;
 
-      if (strchr (symbol_get_obj (sym)->versioned_name,
-                 ELF_VER_CHR) == NULL)
+      ++input_line_pointer;
+      SKIP_WHITESPACE ();
+      if (startswith (input_line_pointer, "local"))
        {
-         as_bad (_("missing version name in `%s' for symbol `%s'"),
-                 symbol_get_obj (sym)->versioned_name,
-                 S_GET_NAME (sym));
-         ignore_rest_of_line ();
-         return;
+         input_line_pointer += 5;
+         sy_obj->visibility = visibility_local;
        }
-    }
-  else
-    {
-      if (strcmp (symbol_get_obj (sym)->versioned_name, name))
+      else if (startswith (input_line_pointer, "hidden"))
        {
-         as_bad (_("multiple versions [`%s'|`%s'] for symbol `%s'"),
-                 name, symbol_get_obj (sym)->versioned_name,
-                 S_GET_NAME (sym));
-         ignore_rest_of_line ();
-         return;
+         input_line_pointer += 6;
+         sy_obj->visibility = visibility_hidden;
        }
-
-      (void) restore_line_pointer (c);
+      else if (startswith (input_line_pointer, "remove"))
+       {
+         input_line_pointer += 6;
+         sy_obj->visibility = visibility_remove;
+       }
+      else
+       input_line_pointer = save;
     }
 
   demand_empty_rest_of_line ();
@@ -1764,7 +1946,7 @@ record_attribute (int vendor, unsigned int tag)
 /* Return true if we have seen an explicit specification of attribute TAG
    for vendor VENDOR.  */
 
-bfd_boolean
+bool
 obj_elf_seen_attribute (int vendor, unsigned int tag)
 {
   unsigned int base;
@@ -1776,7 +1958,7 @@ obj_elf_seen_attribute (int vendor, unsigned int tag)
   for (rai = recorded_attributes; rai; rai = rai->next)
     if (rai->vendor == vendor && rai->base == base)
       return (rai->mask & mask) != 0;
-  return FALSE;
+  return false;
 }
 
 /* Parse an attribute directive for VENDOR.
@@ -1921,6 +2103,22 @@ elf_obj_symbol_new_hook (symbolS *symbolP)
 #endif
 }
 
+/* Deduplicate size expressions.  We might get into trouble with
+   multiple freeing or use after free if we leave them pointing to the
+   same expressionS.  */
+
+void
+elf_obj_symbol_clone_hook (symbolS *newsym, symbolS *orgsym ATTRIBUTE_UNUSED)
+{
+  struct elf_obj_sy *newelf = symbol_get_obj (newsym);
+  if (newelf->size)
+    {
+      expressionS *exp = XNEW (expressionS);
+      *exp = *newelf->size;
+      newelf->size = exp;
+    }
+}
+
 /* When setting one symbol equal to another, by default we probably
    want them to have the same "size", whatever it means in the current
    context.  */
@@ -1938,8 +2136,7 @@ elf_copy_symbol_attributes (symbolS *dest, symbolS *src)
     }
   else
     {
-      if (destelf->size != NULL)
-       free (destelf->size);
+      free (destelf->size);
       destelf->size = NULL;
     }
   S_SET_SIZE (dest, S_GET_SIZE (src));
@@ -2041,11 +2238,8 @@ obj_elf_size (int ignore ATTRIBUTE_UNUSED)
   if (exp.X_op == O_constant)
     {
       S_SET_SIZE (sym, exp.X_add_number);
-      if (symbol_get_obj (sym)->size)
-       {
-         xfree (symbol_get_obj (sym)->size);
-         symbol_get_obj (sym)->size = NULL;
-       }
+      xfree (symbol_get_obj (sym)->size);
+      symbol_get_obj (sym)->size = NULL;
     }
   else
     {
@@ -2168,26 +2362,28 @@ obj_elf_type (int ignore ATTRIBUTE_UNUSED)
           || strcmp (type_name, "10") == 0
           || strcmp (type_name, "STT_GNU_IFUNC") == 0)
     {
-      struct elf_backend_data *bed;
+      const struct elf_backend_data *bed;
 
-      bed = (struct elf_backend_data *) get_elf_backend_data (stdoutput);
-      if (bed->elf_osabi == ELFOSABI_NONE)
-       bed->elf_osabi = ELFOSABI_GNU;
-      else if (bed->elf_osabi != ELFOSABI_GNU
-              && bed->elf_osabi != ELFOSABI_FREEBSD)
+      bed = get_elf_backend_data (stdoutput);
+      if (bed->elf_osabi != ELFOSABI_NONE
+         && bed->elf_osabi != ELFOSABI_GNU
+         && bed->elf_osabi != ELFOSABI_FREEBSD)
        as_bad (_("symbol type \"%s\" is supported only by GNU "
                  "and FreeBSD targets"), type_name);
+      /* MIPS targets do not support IFUNCS.  */
+      else if (bed->target_id == MIPS_ELF_DATA)
+       as_bad (_("symbol type \"%s\" is not supported by "
+                    "MIPS targets"), type_name);
       elf_tdata (stdoutput)->has_gnu_osabi |= elf_gnu_osabi_ifunc;
       type = BSF_FUNCTION | BSF_GNU_INDIRECT_FUNCTION;
     }
   else if (strcmp (type_name, "gnu_unique_object") == 0)
     {
-      struct elf_backend_data *bed;
+      const struct elf_backend_data *bed;
 
-      bed = (struct elf_backend_data *) get_elf_backend_data (stdoutput);
-      if (bed->elf_osabi == ELFOSABI_NONE)
-       bed->elf_osabi = ELFOSABI_GNU;
-      else if (bed->elf_osabi != ELFOSABI_GNU)
+      bed = get_elf_backend_data (stdoutput);
+      if (bed->elf_osabi != ELFOSABI_NONE
+         && bed->elf_osabi != ELFOSABI_GNU)
        as_bad (_("symbol type \"%s\" is supported only by GNU targets"),
                type_name);
       elf_tdata (stdoutput)->has_gnu_osabi |= elf_gnu_osabi_unique;
@@ -2291,12 +2487,13 @@ obj_elf_init_stab_section (segT seg)
   p = frag_more (12);
   /* Zero it out.  */
   memset (p, 0, 12);
-  file = as_where (NULL);
+  file = remap_debug_filename (as_where (NULL));
   stabstr_name = concat (segment_name (seg), "str", (char *) NULL);
-  stroff = get_stab_string_offset (file, stabstr_name, TRUE);
+  stroff = get_stab_string_offset (file, stabstr_name, true);
   know (stroff == 1 || (stroff == 0 && file[0] == '\0'));
   md_number_to_chars (p, stroff, 4);
   seg_info (seg)->stabu.p = p;
+  xfree ((char *) file);
 }
 
 #endif
@@ -2311,7 +2508,7 @@ adjust_stab_sections (bfd *abfd, asection *sec, void *xxx ATTRIBUTE_UNUSED)
   char *p;
   int strsz, nsyms;
 
-  if (strncmp (".stab", sec->name, 5))
+  if (!startswith (sec->name, ".stab"))
     return;
   if (!strcmp ("str", sec->name + strlen (sec->name) - 3))
     return;
@@ -2353,13 +2550,13 @@ elf_ecoff_set_ext (symbolS *sym, struct ecoff_extr *ext)
    supposed to *EXT to the external symbol information, and return
    whether the symbol should be used at all.  */
 
-static bfd_boolean
+static bool
 elf_get_extr (asymbol *sym, EXTR *ext)
 {
   if (sym->udata.p == NULL)
-    return FALSE;
+    return false;
   *ext = *(EXTR *) sym->udata.p;
-  return TRUE;
+  return true;
 }
 
 /* This function is called by bfd_ecoff_debug_externals.  It has
@@ -2378,6 +2575,7 @@ elf_frob_symbol (symbolS *symp, int *puntp)
 {
   struct elf_obj_sy *sy_obj;
   expressionS *size;
+  struct elf_versioned_name_list *versioned_name;
 
 #ifdef NEED_ECOFF_DEBUG
   if (ECOFF_DEBUGGING)
@@ -2405,73 +2603,47 @@ elf_frob_symbol (symbolS *symp, int *puntp)
       sy_obj->size = NULL;
     }
 
-  if (sy_obj->versioned_name != NULL)
+  versioned_name = sy_obj->versioned_name;
+  if (versioned_name)
     {
-      char *p;
-
-      p = strchr (sy_obj->versioned_name, ELF_VER_CHR);
-      if (p == NULL)
-       /* We will have already reported an error about a missing version.  */
-       *puntp = TRUE;
-
       /* This symbol was given a new name with the .symver directive.
-
         If this is an external reference, just rename the symbol to
         include the version string.  This will make the relocs be
-        against the correct versioned symbol.
-
-        If this is a definition, add an alias.  FIXME: Using an alias
-        will permit the debugging information to refer to the right
-        symbol.  However, it's not clear whether it is the best
-        approach.  */
-
-      else if (! S_IS_DEFINED (symp))
+        against the correct versioned symbol.  */
+
+      /* We will have already reported an version error.  */
+      if (sy_obj->bad_version)
+       *puntp = true;
+      /* elf_frob_file_before_adjust only allows one version symbol for
+        renamed symbol.  */
+      else if (sy_obj->rename)
+       S_SET_NAME (symp, versioned_name->name);
+      else if (S_IS_COMMON (symp))
        {
-         /* Verify that the name isn't using the @@ syntax--this is
-            reserved for definitions of the default version to link
-            against.  */
-         if (p[1] == ELF_VER_CHR)
-           {
-             as_bad (_("invalid attempt to declare external version name"
-                       " as default in symbol `%s'"),
-                     sy_obj->versioned_name);
-             *puntp = TRUE;
-           }
-         S_SET_NAME (symp, sy_obj->versioned_name);
+         as_bad (_("`%s' can't be versioned to common symbol '%s'"),
+                 versioned_name->name, S_GET_NAME (symp));
+         *puntp = true;
        }
       else
        {
-         if (p[1] == ELF_VER_CHR && p[2] == ELF_VER_CHR)
-           {
-             size_t l;
-
-             /* The @@@ syntax is a special case. It renames the
-                symbol name to versioned_name with one `@' removed.  */
-             l = strlen (&p[3]) + 1;
-             memmove (&p[2], &p[3], l);
-             S_SET_NAME (symp, sy_obj->versioned_name);
-           }
-         else
+         asymbol *bfdsym;
+         elf_symbol_type *elfsym;
+
+         /* This is a definition.  Add an alias for each version.
+            FIXME: Using an alias will permit the debugging information
+            to refer to the right symbol.  However, it's not clear
+            whether it is the best approach.  */
+
+         /* FIXME: Creating a new symbol here is risky.  We're
+            in the final loop over the symbol table.  We can
+            get away with it only because the symbol goes to
+            the end of the list, where the loop will still see
+            it.  It would probably be better to do this in
+            obj_frob_file_before_adjust.  */
+         for (; versioned_name != NULL;
+              versioned_name = versioned_name->next)
            {
-             symbolS *symp2;
-
-             /* FIXME: Creating a new symbol here is risky.  We're
-                in the final loop over the symbol table.  We can
-                get away with it only because the symbol goes to
-                the end of the list, where the loop will still see
-                it.  It would probably be better to do this in
-                obj_frob_file_before_adjust.  */
-
-             symp2 = symbol_find_or_make (sy_obj->versioned_name);
-
-             /* Now we act as though we saw symp2 = sym.  */
-             if (S_IS_COMMON (symp))
-               {
-                 as_bad (_("`%s' can't be versioned to common symbol '%s'"),
-                         sy_obj->versioned_name, S_GET_NAME (symp));
-                 *puntp = TRUE;
-                 return;
-               }
+             symbolS *symp2 = symbol_find_or_make (versioned_name->name);
 
              S_SET_SEGMENT (symp2, S_GET_SEGMENT (symp));
 
@@ -2479,7 +2651,8 @@ elf_frob_symbol (symbolS *symp, int *puntp)
                 because we are in the middle of the final loop.  */
              S_SET_VALUE (symp2,
                           (S_GET_VALUE (symp)
-                           - symbol_get_frag (symp)->fr_address));
+                           - (symbol_get_frag (symp)->fr_address
+                              / OCTETS_PER_BYTE)));
 
              symbol_set_frag (symp2, symbol_get_frag (symp));
 
@@ -2494,6 +2667,24 @@ elf_frob_symbol (symbolS *symp, int *puntp)
              if (S_IS_EXTERNAL (symp))
                S_SET_EXTERNAL (symp2);
            }
+
+         switch (symbol_get_obj (symp)->visibility)
+           {
+           case visibility_unchanged:
+             break;
+           case visibility_hidden:
+             bfdsym = symbol_get_bfdsym (symp);
+             elfsym = elf_symbol_from (bfdsym);
+             elfsym->internal_elf_sym.st_other &= ~3;
+             elfsym->internal_elf_sym.st_other |= STV_HIDDEN;
+             break;
+           case visibility_remove:
+             symbol_remove (symp, &symbol_rootP, &symbol_lastP);
+             break;
+           case visibility_local:
+             S_CLEAR_EXTERNAL (symp);
+             break;
+           }
        }
     }
 
@@ -2510,7 +2701,7 @@ struct group_list
 {
   asection **head;             /* Section lists.  */
   unsigned int num_group;      /* Number of lists.  */
-  struct hash_control *indexes; /* Maps group name to index in head array.  */
+  htab_t indexes; /* Maps group name to index in head array.  */
 };
 
 static struct group_list groups;
@@ -2548,7 +2739,7 @@ build_additional_section_info (bfd *abfd ATTRIBUTE_UNUSED,
 
   /* If this group already has a list, add the section to the head of
      the list.  */
-  elem_idx = (unsigned int *) hash_find (list->indexes, group_name);
+  elem_idx = (unsigned int *) str_hash_find (list->indexes, group_name);
   if (elem_idx != NULL)
     {
       elf_next_in_group (sec) = list->head[*elem_idx];
@@ -2570,12 +2761,15 @@ build_additional_section_info (bfd *abfd ATTRIBUTE_UNUSED,
   /* Add index to hash.  */
   idx_ptr = XNEW (unsigned int);
   *idx_ptr = i;
-  hash_insert (list->indexes, group_name, idx_ptr);
+  str_hash_insert (list->indexes, group_name, idx_ptr, 0);
 }
 
-static void free_section_idx (const char *key ATTRIBUTE_UNUSED, void *val)
+static int
+free_section_idx (void **slot, void *arg ATTRIBUTE_UNUSED)
 {
-  free ((unsigned int *) val);
+  string_tuple_t *tuple = *((string_tuple_t **) slot);
+  free ((char *)tuple->value);
+  return 1;
 }
 
 /* Create symbols for group signature.  */
@@ -2588,7 +2782,7 @@ elf_adjust_symtab (void)
   /* Go find section groups.  */
   groups.num_group = 0;
   groups.head = NULL;
-  groups.indexes = hash_new ();
+  groups.indexes = str_htab_create ();
   bfd_map_over_sections (stdoutput, build_additional_section_info,
                         &groups);
 
@@ -2637,7 +2831,7 @@ elf_adjust_symtab (void)
       if (!sy || !symbol_on_chain (sy, symbol_rootP, symbol_lastP))
        {
          /* Create the symbol now.  */
-         sy = symbol_new (group_name, now_seg, (valueT) 0, frag_now);
+         sy = symbol_new (group_name, now_seg, frag_now, 0);
 #ifdef TE_SOLARIS
          /* Before Solaris 11 build 154, Sun ld rejects local group
             signature symbols, so make them weak hidden instead.  */
@@ -2649,6 +2843,9 @@ elf_adjust_symtab (void)
          symbol_table_insert (sy);
        }
       elf_group_id (s) = symbol_get_bfdsym (sy);
+      /* Mark the group signature symbol as used so that it will be
+        included in the symbol table.  */
+      symbol_mark_used_in_reloc (sy);
     }
 }
 
@@ -2672,36 +2869,61 @@ elf_frob_file_before_adjust (void)
       symbolS *symp;
 
       for (symp = symbol_rootP; symp; symp = symbol_next (symp))
-       if (!S_IS_DEFINED (symp))
-         {
-           if (symbol_get_obj (symp)->versioned_name)
-             {
-               char *p;
-
-               /* The @@@ syntax is a special case. If the symbol is
-                  not defined, 2 `@'s will be removed from the
-                  versioned_name.  */
-
-               p = strchr (symbol_get_obj (symp)->versioned_name,
-                           ELF_VER_CHR);
-               if (p != NULL && p[1] == ELF_VER_CHR && p[2] == ELF_VER_CHR)
-                 {
-                   size_t l = strlen (&p[3]) + 1;
-                   memmove (&p[1], &p[3], l);
-                 }
-               if (symbol_used_p (symp) == 0
-                   && symbol_used_in_reloc_p (symp) == 0)
-                 symbol_remove (symp, &symbol_rootP, &symbol_lastP);
-             }
+       {
+         struct elf_obj_sy *sy_obj = symbol_get_obj (symp);
+         int is_defined = !!S_IS_DEFINED (symp);
 
-           /* If there was .weak foo, but foo was neither defined nor
-              used anywhere, remove it.  */
+         if (sy_obj->versioned_name)
+           {
+             char *p = strchr (sy_obj->versioned_name->name,
+                               ELF_VER_CHR);
 
-           else if (S_IS_WEAK (symp)
-                    && symbol_used_p (symp) == 0
-                    && symbol_used_in_reloc_p (symp) == 0)
-             symbol_remove (symp, &symbol_rootP, &symbol_lastP);
-         }
+             if (sy_obj->rename)
+               {
+                 /* The @@@ syntax is a special case. If the symbol is
+                    not defined, 2 `@'s will be removed from the
+                    versioned_name. Otherwise, 1 `@' will be removed.   */
+                 size_t l = strlen (&p[3]) + 1;
+                 memmove (&p[1 + is_defined], &p[3], l);
+               }
+
+             if (!is_defined)
+               {
+                 /* Verify that the name isn't using the @@ syntax--this
+                    is reserved for definitions of the default version
+                    to link against.  */
+                 if (!sy_obj->rename && p[1] == ELF_VER_CHR)
+                   {
+                     as_bad (_("invalid attempt to declare external "
+                               "version name as default in symbol `%s'"),
+                             sy_obj->versioned_name->name);
+                     return;
+                   }
+
+                 /* Only one version symbol is allowed for undefined
+                    symbol.  */
+                 if (sy_obj->versioned_name->next)
+                   {
+                     as_bad (_("multiple versions [`%s'|`%s'] for "
+                               "symbol `%s'"),
+                             sy_obj->versioned_name->name,
+                             sy_obj->versioned_name->next->name,
+                             S_GET_NAME (symp));
+                     return;
+                   }
+
+                 sy_obj->rename = true;
+               }
+           }
+
+         /* If there was .symver or .weak, but symbol was neither
+            defined nor used anywhere, remove it.  */
+         if (!is_defined
+             && (sy_obj->versioned_name || S_IS_WEAK (symp))
+             && symbol_used_p (symp) == 0
+             && symbol_used_in_reloc_p (symp) == 0)
+           symbol_remove (symp, &symbol_rootP, &symbol_lastP);
+       }
     }
 }
 
@@ -2735,8 +2957,8 @@ elf_frob_file_after_relocs (void)
     }
 
   /* Cleanup hash.  */
-  hash_traverse (groups.indexes, free_section_idx);
-  hash_die (groups.indexes);
+  htab_traverse (groups.indexes, free_section_idx, NULL);
+  htab_delete (groups.indexes);
 
 #ifdef NEED_ECOFF_DEBUG
   if (ECOFF_DEBUGGING)
@@ -2772,7 +2994,7 @@ elf_frob_file_after_relocs (void)
       /* Set up the external symbols.  */
       debug.ssext = debug.ssext_end = NULL;
       debug.external_ext = debug.external_ext_end = NULL;
-      if (! bfd_ecoff_debug_externals (stdoutput, &debug, debug_swap, TRUE,
+      if (! bfd_ecoff_debug_externals (stdoutput, &debug, debug_swap, true,
                                       elf_get_extr, elf_set_index))
        as_fatal (_("failed to set up debugging information: %s"),
                  bfd_errmsg (bfd_get_error ()));
@@ -2884,6 +3106,6 @@ const struct format_ops elf_format_ops =
 #endif
   elf_obj_read_begin_hook,
   elf_obj_symbol_new_hook,
-  0,
+  elf_obj_symbol_clone_hook,
   elf_adjust_symtab
 };