Fix overflow detection in the Z80 assembler.
[binutils-gdb.git] / gas / config / obj-elf.c
index d0fbcfb43ab6514a39350c5a8c78f556ef0888da..e93d59cb081c1ac180dc72d6c226cad4bac44f6c 100644 (file)
@@ -1,7 +1,5 @@
 /* ELF object file format
 /* ELF object file format
-   Copyright 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000,
-   2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
-   Free Software Foundation, Inc.
+   Copyright (C) 1992-2021 Free Software Foundation, Inc.
 
    This file is part of GAS, the GNU Assembler.
 
 
    This file is part of GAS, the GNU Assembler.
 
@@ -25,7 +23,6 @@
 #include "safe-ctype.h"
 #include "subsegs.h"
 #include "obstack.h"
 #include "safe-ctype.h"
 #include "subsegs.h"
 #include "obstack.h"
-#include "struc-symbol.h"
 #include "dwarf2dbg.h"
 
 #ifndef ECOFF_DEBUGGING
 #include "dwarf2dbg.h"
 
 #ifndef ECOFF_DEBUGGING
@@ -36,6 +33,7 @@
 
 #ifdef NEED_ECOFF_DEBUG
 #include "ecoff.h"
 
 #ifdef NEED_ECOFF_DEBUG
 #include "ecoff.h"
+#include "bfd/ecoff-bfd.h"
 #endif
 
 #ifdef TC_ALPHA
 #endif
 
 #ifdef TC_ALPHA
 #include "elf/ppc.h"
 #endif
 
 #include "elf/ppc.h"
 #endif
 
-#ifdef TC_I370
-#include "elf/i370.h"
-#endif
-
 #ifdef TC_I386
 #include "elf/x86-64.h"
 #endif
 #ifdef TC_I386
 #include "elf/x86-64.h"
 #endif
 #include "elf/mep.h"
 #endif
 
 #include "elf/mep.h"
 #endif
 
+#ifdef TC_NIOS2
+#include "elf/nios2.h"
+#endif
+
+#ifdef TC_PRU
+#include "elf/pru.h"
+#endif
+
 static void obj_elf_line (int);
 static void obj_elf_size (int);
 static void obj_elf_type (int);
 static void obj_elf_line (int);
 static void obj_elf_size (int);
 static void obj_elf_type (int);
@@ -72,12 +74,15 @@ static void obj_elf_visibility (int);
 static void obj_elf_symver (int);
 static void obj_elf_subsection (int);
 static void obj_elf_popsection (int);
 static void obj_elf_symver (int);
 static void obj_elf_subsection (int);
 static void obj_elf_popsection (int);
+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_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[] =
 {
 
 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},
   {"comm", obj_elf_common, 0},
   {"common", obj_elf_common, 1},
   {"ident", obj_elf_ident, 0},
@@ -110,15 +115,14 @@ static const pseudo_typeS elf_pseudo_table[] =
   {"subsection", obj_elf_subsection, 0},
 
   /* These are GNU extensions to aid in garbage collecting C++ vtables.  */
   {"subsection", obj_elf_subsection, 0},
 
   /* These are GNU extensions to aid in garbage collecting C++ vtables.  */
-  {"vtable_inherit", (void (*) (int)) &obj_elf_vtable_inherit, 0},
-  {"vtable_entry", (void (*) (int)) &obj_elf_vtable_entry, 0},
+  {"vtable_inherit", obj_elf_vtable_inherit, 0},
+  {"vtable_entry", obj_elf_vtable_entry, 0},
+
+  /* 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.  */
   /* These are used for dwarf2.  */
-  { "file", (void (*) (int)) dwarf2_directive_file, 0 },
+  { "file", dwarf2_directive_file, 0 },
   { "loc",  dwarf2_directive_loc,  0 },
   { "loc_mark_labels", dwarf2_directive_loc_mark_labels, 0 },
 
   { "loc",  dwarf2_directive_loc,  0 },
   { "loc_mark_labels", dwarf2_directive_loc_mark_labels, 0 },
 
@@ -127,6 +131,7 @@ static const pseudo_typeS elf_pseudo_table[] =
   {"offset", obj_elf_struct, 0},
   {"struct", obj_elf_struct, 0},
   {"text", obj_elf_text, 0},
   {"offset", obj_elf_struct, 0},
   {"struct", obj_elf_struct, 0},
   {"text", obj_elf_text, 0},
+  {"bss", obj_elf_bss, 0},
 
   {"tls_common", obj_elf_tls_common, 0},
 
 
   {"tls_common", obj_elf_tls_common, 0},
 
@@ -154,6 +159,7 @@ static const pseudo_typeS ecoff_debug_pseudo_table[] =
   { "etype",   ecoff_directive_type,   0 },
 
   /* ECOFF specific debugging information.  */
   { "etype",   ecoff_directive_type,   0 },
 
   /* ECOFF specific debugging information.  */
+  { "aent",    ecoff_directive_ent,    1 },
   { "begin",   ecoff_directive_begin,  0 },
   { "bend",    ecoff_directive_bend,   0 },
   { "end",     ecoff_directive_end,    0 },
   { "begin",   ecoff_directive_begin,  0 },
   { "bend",    ecoff_directive_bend,   0 },
   { "end",     ecoff_directive_end,    0 },
@@ -254,16 +260,17 @@ elf_sec_sym_ok_for_reloc (asection *sec)
 void
 elf_file_symbol (const char *s, int appfile)
 {
 void
 elf_file_symbol (const char *s, int appfile)
 {
+  asymbol *bsym;
+
   if (!appfile
       || symbol_rootP == NULL
   if (!appfile
       || symbol_rootP == NULL
-      || symbol_rootP->bsym == NULL
-      || (symbol_rootP->bsym->flags & BSF_FILE) == 0)
+      || (bsym = symbol_get_bfdsym (symbol_rootP)) == NULL
+      || (bsym->flags & BSF_FILE) == 0)
     {
       symbolS *sym;
     {
       symbolS *sym;
-      unsigned int name_length;
+      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)))
 
       name_length = strlen (s);
       if (name_length > strlen (S_GET_NAME (sym)))
@@ -276,14 +283,17 @@ elf_file_symbol (const char *s, int appfile)
 
       symbol_get_bfdsym (sym)->flags |= BSF_FILE;
 
 
       symbol_get_bfdsym (sym)->flags |= BSF_FILE;
 
-      if (symbol_rootP != sym)
+      if (symbol_rootP != sym
+         && ((bsym = symbol_get_bfdsym (symbol_rootP)) == NULL
+             || (bsym->flags & BSF_FILE) == 0))
        {
          symbol_remove (sym, &symbol_rootP, &symbol_lastP);
          symbol_insert (sym, symbol_rootP, &symbol_rootP, &symbol_lastP);
        {
          symbol_remove (sym, &symbol_rootP, &symbol_lastP);
          symbol_insert (sym, symbol_rootP, &symbol_rootP, &symbol_lastP);
+       }
+
 #ifdef DEBUG
 #ifdef DEBUG
-         verify_symbol_chain (symbol_rootP, symbol_lastP);
+      verify_symbol_chain (symbol_rootP, symbol_lastP);
 #endif
 #endif
-       }
     }
 
 #ifdef NEED_ECOFF_DEBUG
     }
 
 #ifdef NEED_ECOFF_DEBUG
@@ -315,9 +325,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 (*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;
            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
            {
            input_line_pointer += 5;
          else
            {
@@ -400,11 +410,10 @@ get_sym_from_input_line_and_check (void)
   char c;
   symbolS *sym;
 
   char c;
   symbolS *sym;
 
-  name = input_line_pointer;
-  c = get_symbol_end ();
+  c = get_symbol_name (& name);
   sym = symbol_find_or_make (name);
   *input_line_pointer = c;
   sym = symbol_find_or_make (name);
   *input_line_pointer = c;
-  SKIP_WHITESPACE ();
+  SKIP_WHITESPACE_AFTER_NAME ();
 
   /* There is no symbol name if input_line_pointer has not moved.  */
   if (name == input_line_pointer)
 
   /* There is no symbol name if input_line_pointer has not moved.  */
   if (name == input_line_pointer)
@@ -472,7 +481,7 @@ obj_elf_visibility (int visibility)
       symbolP = get_sym_from_input_line_and_check ();
 
       bfdsym = symbol_get_bfdsym (symbolP);
       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);
 
 
       gas_assert (elfsym);
 
@@ -507,16 +516,37 @@ struct section_stack
 
 static struct section_stack *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)
 {
 {
-  const char *gname = (const char *) 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 *group_name = elf_group_name (sec);
-
-  return (group_name == gname
-         || (group_name != NULL
-             && gname != NULL
-             && strcmp (group_name, gname) == 0));
+  const char *linked_to_symbol_name
+    = sec->map_head.linked_to_symbol_name;
+  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 (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
+         && (group_name == gname
+             || (group_name != NULL
+                 && gname != NULL
+                 && strcmp (group_name, gname) == 0))
+         && (linked_to_symbol_name == match->linked_to_symbol_name
+             || (linked_to_symbol_name != NULL
+                 && match->linked_to_symbol_name != NULL
+                 && strcmp (linked_to_symbol_name,
+                            match->linked_to_symbol_name) == 0)));
 }
 
 /* Handle the .section pseudo-op.  This code supports two different
 }
 
 /* Handle the .section pseudo-op.  This code supports two different
@@ -539,10 +569,10 @@ get_section (bfd *abfd ATTRIBUTE_UNUSED, asection *sec, void *inf)
 
 void
 obj_elf_change_section (const char *name,
 
 void
 obj_elf_change_section (const char *name,
-                       int type,
+                       unsigned int type,
                        bfd_vma attr,
                        int entsize,
                        bfd_vma attr,
                        int entsize,
-                       const char *group_name,
+                       struct elf_section_match *match_p,
                        int linkonce,
                        int push)
 {
                        int linkonce,
                        int push)
 {
@@ -552,6 +582,12 @@ obj_elf_change_section (const char *name,
   const struct elf_backend_data *bed;
   const struct bfd_elf_special_section *ssect;
 
   const struct elf_backend_data *bed;
   const struct bfd_elf_special_section *ssect;
 
+  if (match_p == NULL)
+    {
+      static struct elf_section_match unused_match;
+      match_p = &unused_match;
+    }
+
 #ifdef md_flush_pending_output
   md_flush_pending_output ();
 #endif
 #ifdef md_flush_pending_output
   md_flush_pending_output ();
 #endif
@@ -560,7 +596,7 @@ obj_elf_change_section (const char *name,
   if (push)
     {
       struct section_stack *elt;
   if (push)
     {
       struct section_stack *elt;
-      elt = (struct section_stack *) xmalloc (sizeof (struct section_stack));
+      elt = XNEW (struct section_stack);
       elt->next = section_stack;
       elt->seg = now_seg;
       elt->prev_seg = previous_section;
       elt->next = section_stack;
       elt->seg = now_seg;
       elt->prev_seg = previous_section;
@@ -568,11 +604,11 @@ obj_elf_change_section (const char *name,
       elt->prev_subseg = previous_subsection;
       section_stack = elt;
     }
       elt->prev_subseg = previous_subsection;
       section_stack = elt;
     }
-  previous_section = now_seg;
-  previous_subsection = now_subseg;
 
 
-  old_sec = bfd_get_section_by_name_if (stdoutput, name, get_section,
-                                       (void *) group_name);
+  obj_elf_section_change_hook ();
+
+  old_sec = bfd_get_section_by_name_if (stdoutput, name, get_section_by_match,
+                                       (void *) match_p);
   if (old_sec)
     {
       sec = old_sec;
   if (old_sec)
     {
       sec = old_sec;
@@ -586,7 +622,7 @@ obj_elf_change_section (const char *name,
 
   if (ssect != NULL)
     {
 
   if (ssect != NULL)
     {
-      bfd_boolean override = FALSE;
+      bool override = false;
 
       if (type == SHT_NULL)
        type = ssect->type;
 
       if (type == SHT_NULL)
        type = ssect->type;
@@ -613,7 +649,9 @@ obj_elf_change_section (const char *name,
              && ssect->type != SHT_PREINIT_ARRAY)
            {
              /* We allow to specify any type for a .note section.  */
              && ssect->type != SHT_PREINIT_ARRAY)
            {
              /* We allow to specify any type for a .note section.  */
-             if (ssect->type != SHT_NOTE)
+             if (ssect->type != SHT_NOTE
+                 /* Processor and application defined types are allowed too.  */
+                 && type < SHT_LOPROC)
                as_warn (_("setting incorrect section type for %s"),
                         name);
            }
                as_warn (_("setting incorrect section type for %s"),
                         name);
            }
@@ -625,41 +663,50 @@ obj_elf_change_section (const char *name,
            }
        }
 
            }
        }
 
-      if (old_sec == NULL && (attr & ~ssect->attr) != 0)
+      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
          /* 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] == '.'
            ;
          /* 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.  */
                       & ~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))
                   && (strcmp (name, ".interp") == 0
                       || strcmp (name, ".strtab") == 0
                       || strcmp (name, ".symtab") == 0))
-           override = TRUE;
+           override = true;
          /* .note.GNU-stack can have SHF_EXECINSTR.  */
          /* .note.GNU-stack can have SHF_EXECINSTR.  */
-         else if (attr == SHF_EXECINSTR
+         else if (generic_attr == SHF_EXECINSTR
                   && strcmp (name, ".note.GNU-stack") == 0)
                   && strcmp (name, ".note.GNU-stack") == 0)
-           override = TRUE;
+           override = true;
 #ifdef TC_ALPHA
          /* A section on Alpha may have SHF_ALPHA_GPREL.  */
 #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
 #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))
                   && (ssect->type == SHT_INIT_ARRAY
                       || ssect->type == SHT_FINI_ARRAY
                       || ssect->type == SHT_PREINIT_ARRAY))
@@ -668,12 +715,13 @@ obj_elf_change_section (const char *name,
 #endif
          else
            {
 #endif
          else
            {
-             if (group_name == NULL)
+             if (match_p->group_name == NULL)
                as_warn (_("setting incorrect section attributes for %s"),
                         name);
                as_warn (_("setting incorrect section attributes for %s"),
                         name);
-             override = TRUE;
+             override = true;
            }
        }
            }
        }
+
       if (!override && old_sec == NULL)
        attr |= ssect->attr;
     }
       if (!override && old_sec == NULL)
        attr |= ssect->attr;
     }
@@ -703,20 +751,36 @@ 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;
        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->sh_info;
 
       /* Prevent SEC_HAS_CONTENTS from being inadvertently set.  */
       if (type == SHT_NOBITS)
        seg_info (sec)->bss = 1;
 
 
       /* Prevent SEC_HAS_CONTENTS from being inadvertently set.  */
       if (type == SHT_NOBITS)
        seg_info (sec)->bss = 1;
 
-      bfd_set_section_flags (stdoutput, sec, flags);
+      /* Set the section ID and flags.  */
+      sec->section_id = match_p->section_id;
+      flags |= match_p->flags;
+
+      /* Set the linked-to symbol name.  */
+      sec->map_head.linked_to_symbol_name
+       = match_p->linked_to_symbol_name;
+
+      bfd_set_section_flags (sec, flags);
       if (flags & SEC_MERGE)
        sec->entsize = entsize;
       if (flags & SEC_MERGE)
        sec->entsize = entsize;
-      elf_group_name (sec) = group_name;
+      elf_group_name (sec) = match_p->group_name;
 
       /* Add a symbol for this section to the symbol table.  */
       secsym = symbol_find (name);
       if (secsym != NULL)
 
       /* 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));
     }
       else
        symbol_table_insert (section_symbol (sec));
     }
@@ -724,7 +788,17 @@ obj_elf_change_section (const char *name,
     {
       if (type != SHT_NULL
          && (unsigned) type != elf_section_type (old_sec))
     {
       if (type != SHT_NULL
          && (unsigned) type != elf_section_type (old_sec))
-       as_warn (_("ignoring changed section type for %s"), name);
+       {
+         if (ssect != NULL)
+           /* This is a special section with known type.  User
+              assembly might get the section type wrong; Even high
+              profile projects like glibc have done so in the past.
+              So don't error in this case.  */
+           as_warn (_("ignoring changed section type for %s"), name);
+         else
+           /* Do error when assembly isn't self-consistent.  */
+           as_bad (_("changed section type for %s"), name);
+       }
 
       if (attr != 0)
        {
 
       if (attr != 0)
        {
@@ -736,9 +810,19 @@ obj_elf_change_section (const char *name,
                  | SEC_EXCLUDE | SEC_SORT_ENTRIES | SEC_MERGE | SEC_STRINGS
                  | SEC_LINK_ONCE | SEC_LINK_DUPLICATES_DISCARD
                  | SEC_THREAD_LOCAL)))
                  | SEC_EXCLUDE | SEC_SORT_ENTRIES | SEC_MERGE | SEC_STRINGS
                  | SEC_LINK_ONCE | SEC_LINK_DUPLICATES_DISCARD
                  | SEC_THREAD_LOCAL)))
-           as_warn (_("ignoring changed section attributes for %s"), name);
+           {
+             if (ssect != NULL)
+               as_warn (_("ignoring changed section attributes for %s"), name);
+             else
+               as_bad (_("changed section attributes for %s"), name);
+           }
+         else
+           /* FIXME: Maybe we should consider removing a previously set
+              processor or application specific attribute as suspicious?  */
+           elf_section_flags (sec) = attr;
+
          if ((flags & SEC_MERGE) && old_sec->entsize != (unsigned) entsize)
          if ((flags & SEC_MERGE) && old_sec->entsize != (unsigned) entsize)
-           as_warn (_("ignoring changed section entity size for %s"), name);
+           as_bad (_("changed section entity size for %s"), name);
        }
     }
 
        }
     }
 
@@ -748,10 +832,11 @@ obj_elf_change_section (const char *name,
 }
 
 static bfd_vma
 }
 
 static bfd_vma
-obj_elf_parse_section_letters (char *str, size_t len, bfd_boolean *is_clone)
+obj_elf_parse_section_letters (char *str, size_t len,
+                              bool *is_clone, bfd_vma *gnu_attr)
 {
   bfd_vma attr = 0;
 {
   bfd_vma attr = 0;
-  *is_clone = FALSE;
+  *is_clone = false;
 
   while (len > 0)
     {
 
   while (len > 0)
     {
@@ -763,6 +848,9 @@ obj_elf_parse_section_letters (char *str, size_t len, bfd_boolean *is_clone)
        case 'e':
          attr |= SHF_EXCLUDE;
          break;
        case 'e':
          attr |= SHF_EXCLUDE;
          break;
+       case 'o':
+         attr |= SHF_LINK_ORDER;
+         break;
        case 'w':
          attr |= SHF_WRITE;
          break;
        case 'w':
          attr |= SHF_WRITE;
          break;
@@ -781,8 +869,14 @@ obj_elf_parse_section_letters (char *str, size_t len, bfd_boolean *is_clone)
        case 'T':
          attr |= SHF_TLS;
          break;
        case 'T':
          attr |= SHF_TLS;
          break;
+       case 'd':
+         *gnu_attr |= SHF_GNU_MBIND;
+         break;
+       case 'R':
+         *gnu_attr |= SHF_GNU_RETAIN;
+         break;
        case '?':
        case '?':
-         *is_clone = TRUE;
+         *is_clone = true;
          break;
        /* Compatibility.  */
        case 'm':
          break;
        /* Compatibility.  */
        case 'm':
@@ -796,16 +890,54 @@ obj_elf_parse_section_letters (char *str, size_t len, bfd_boolean *is_clone)
                }
              break;
            }
                }
              break;
            }
+         /* Fall through.  */
        default:
          {
        default:
          {
-           char *bad_msg = _("unrecognized .section attribute: want a,e,w,x,M,S,G,T");
+           const char *bad_msg = _("unrecognized .section attribute:"
+                                   " want a,e,o,w,x,M,S,G,T or number");
 #ifdef md_elf_section_letter
            bfd_vma md_attr = md_elf_section_letter (*str, &bad_msg);
            if (md_attr != (bfd_vma) -1)
              attr |= md_attr;
            else
 #endif
 #ifdef md_elf_section_letter
            bfd_vma md_attr = md_elf_section_letter (*str, &bad_msg);
            if (md_attr != (bfd_vma) -1)
              attr |= md_attr;
            else
 #endif
-             as_fatal ("%s", bad_msg);
+             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);
+                   }
+
+                 /* Update str and len, allowing for the fact that
+                    we will execute str++ and len-- below.  */
+                 end --;
+                 len -= (end - str);
+                 str = end;
+               }
+             else
+               as_fatal ("%s", bad_msg);
          }
          break;
        }
          }
          break;
        }
@@ -816,19 +948,19 @@ obj_elf_parse_section_letters (char *str, size_t len, bfd_boolean *is_clone)
 }
 
 static int
 }
 
 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;
     return SHT_PROGBITS;
-  if (len == 6 && strncmp (str, "nobits", 6) == 0)
+  if (len == 6 && startswith (str, "nobits"))
     return SHT_NOBITS;
     return SHT_NOBITS;
-  if (len == 4 && strncmp (str, "note", 4) == 0)
+  if (len == 4 && startswith (str, "note"))
     return SHT_NOTE;
     return SHT_NOTE;
-  if (len == 10 && strncmp (str, "init_array", 10) == 0)
+  if (len == 10 && startswith (str, "init_array"))
     return SHT_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;
     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
     return SHT_PREINIT_ARRAY;
 
 #ifdef md_elf_section_type
@@ -839,6 +971,17 @@ obj_elf_section_type (char *str, size_t len, bfd_boolean warn)
   }
 #endif
 
   }
 #endif
 
+  if (ISDIGIT (*str))
+    {
+      char * end;
+      int type = strtoul (str, & end, 0);
+
+      if (warn && (size_t) (end - str) != len)
+       as_warn (_("extraneous characters at end of numeric section type"));
+
+      return type;
+    }
+
   if (warn)
     as_warn (_("unrecognized section type"));
   return 0;
   if (warn)
     as_warn (_("unrecognized section type"));
   return 0;
@@ -849,15 +992,15 @@ obj_elf_section_word (char *str, size_t len, int *type)
 {
   int ret;
 
 {
   int ret;
 
-  if (len == 5 && strncmp (str, "write", 5) == 0)
+  if (len == 5 && startswith (str, "write"))
     return SHF_WRITE;
     return SHF_WRITE;
-  if (len == 5 && strncmp (str, "alloc", 5) == 0)
+  if (len == 5 && startswith (str, "alloc"))
     return SHF_ALLOC;
     return SHF_ALLOC;
-  if (len == 9 && strncmp (str, "execinstr", 9) == 0)
+  if (len == 9 && startswith (str, "execinstr"))
     return SHF_EXECINSTR;
     return SHF_EXECINSTR;
-  if (len == 7 && strncmp (str, "exclude", 7) == 0)
+  if (len == 7 && startswith (str, "exclude"))
     return SHF_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
     return SHF_TLS;
 
 #ifdef md_elf_section_word
@@ -868,7 +1011,7 @@ obj_elf_section_word (char *str, size_t len, int *type)
   }
 #endif
 
   }
 #endif
 
-  ret = obj_elf_section_type (str, len, FALSE);
+  ret = obj_elf_section_type (str, len, false);
   if (ret != 0)
     *type = ret;
   else
   if (ret != 0)
     *type = ret;
   else
@@ -878,7 +1021,7 @@ obj_elf_section_word (char *str, size_t len, int *type)
 }
 
 /* Get name of section.  */
 }
 
 /* Get name of section.  */
-char *
+const char *
 obj_elf_section_name (void)
 {
   char *name;
 obj_elf_section_name (void)
 {
   char *name;
@@ -908,9 +1051,28 @@ obj_elf_section_name (void)
          return NULL;
        }
 
          return NULL;
        }
 
-      name = (char *) xmalloc (end - input_line_pointer + 1);
-      memcpy (name, input_line_pointer, end - input_line_pointer);
-      name[end - input_line_pointer] = '\0';
+      name = xmemdup0 (input_line_pointer, end - input_line_pointer);
+
+      while (flag_sectname_subst)
+        {
+         char *subst = strchr (name, '%');
+         if (subst && subst[1] == 'S')
+           {
+             int oldlen = strlen (name);
+             int substlen = strlen (now_seg->name);
+             int newlen = oldlen - 2 + substlen;
+             char *newname = XNEWVEC (char, newlen + 1);
+             int headlen = subst - name;
+             memcpy (newname, name, headlen);
+             strcpy (newname + headlen, now_seg->name);
+             strcat (newname + headlen, subst + 2);
+             xfree (name);
+             name = newname;
+           }
+         else
+           break;
+       }
+
 #ifdef tc_canonicalize_section_name
       name = tc_canonicalize_section_name (name);
 #endif
 #ifdef tc_canonicalize_section_name
       name = tc_canonicalize_section_name (name);
 #endif
@@ -920,17 +1082,42 @@ obj_elf_section_name (void)
   return name;
 }
 
   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)
 {
 void
 obj_elf_section (int push)
 {
-  char *name, *group_name, *beg;
+  const char *name;
+  char *beg;
   int type, dummy;
   bfd_vma attr;
   int type, dummy;
   bfd_vma attr;
+  bfd_vma gnu_attr;
   int entsize;
   int linkonce;
   subsegT new_subsection = -1;
   int entsize;
   int linkonce;
   subsegT new_subsection = -1;
+  struct elf_section_match match;
+  unsigned long linked_to_section_index = -1UL;
 
 
-#ifndef TC_I370
   if (flag_mri)
     {
       char mri_type;
   if (flag_mri)
     {
       char mri_type;
@@ -939,8 +1126,7 @@ obj_elf_section (int push)
       md_flush_pending_output ();
 #endif
 
       md_flush_pending_output ();
 #endif
 
-      previous_section = now_seg;
-      previous_subsection = now_subseg;
+      obj_elf_section_change_hook ();
 
       s_mri_sect (&mri_type);
 
 
       s_mri_sect (&mri_type);
 
@@ -950,14 +1136,27 @@ obj_elf_section (int push)
 
       return;
     }
 
       return;
     }
-#endif /* ! defined (TC_I370) */
 
   name = obj_elf_section_name ();
   if (name == NULL)
     return;
 
   name = obj_elf_section_name ();
   if (name == NULL)
     return;
+
+  memset (&match, 0, sizeof (match));
+
+  symbolS * sym;
+  if ((sym = symbol_find (name)) != NULL
+      && ! symbol_section_p (sym)
+      && S_IS_DEFINED (sym)
+      && ! S_IS_VOLATILE (sym)
+      && ! S_CAN_BE_REDEFINED (sym))
+    {
+      as_bad (_("section name '%s' already defined as another symbol"), name);
+      ignore_rest_of_line ();
+      return;
+    }
   type = SHT_NULL;
   attr = 0;
   type = SHT_NULL;
   attr = 0;
-  group_name = NULL;
+  gnu_attr = 0;
   entsize = 0;
   linkonce = 0;
 
   entsize = 0;
   linkonce = 0;
 
@@ -985,7 +1184,7 @@ obj_elf_section (int push)
 
       if (*input_line_pointer == '"')
        {
 
       if (*input_line_pointer == '"')
        {
-         bfd_boolean is_clone;
+         bool is_clone;
 
          beg = demand_copy_C_string (&dummy);
          if (beg == NULL)
 
          beg = demand_copy_C_string (&dummy);
          if (beg == NULL)
@@ -993,7 +1192,8 @@ obj_elf_section (int push)
              ignore_rest_of_line ();
              return;
            }
              ignore_rest_of_line ();
              return;
            }
-         attr |= obj_elf_parse_section_letters (beg, strlen (beg), &is_clone);
+         attr |= obj_elf_parse_section_letters (beg, strlen (beg),
+                                                &is_clone, &gnu_attr);
 
          SKIP_WHITESPACE ();
          if (*input_line_pointer == ',')
 
          SKIP_WHITESPACE ();
          if (*input_line_pointer == ',')
@@ -1012,14 +1212,22 @@ obj_elf_section (int push)
                      ignore_rest_of_line ();
                      return;
                    }
                      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 == '%')
                {
                }
              else if (c == '@' || c == '%')
                {
-                 beg = ++input_line_pointer;
-                 c = get_symbol_end ();
-                 *input_line_pointer = c;
-                 type = obj_elf_section_type (beg, input_line_pointer - beg, TRUE);
+                 ++input_line_pointer;
+
+                 if (ISDIGIT (* input_line_pointer))
+                   type = strtoul (input_line_pointer, &input_line_pointer, 0);
+                 else
+                   {
+                     c = get_symbol_name (& beg);
+                     (void) restore_line_pointer (c);
+                     type = obj_elf_section_type (beg,
+                                                  input_line_pointer - beg,
+                                                  true);
+                   }
                }
              else
                input_line_pointer = save;
                }
              else
                input_line_pointer = save;
@@ -1045,28 +1253,51 @@ obj_elf_section (int push)
              attr &= ~SHF_MERGE;
            }
 
              attr &= ~SHF_MERGE;
            }
 
+         if ((attr & SHF_LINK_ORDER) != 0 && *input_line_pointer == ',')
+           {
+             ++input_line_pointer;
+             SKIP_WHITESPACE ();
+             /* 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"));
          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;
          if ((attr & SHF_GROUP) != 0 && *input_line_pointer == ',')
            {
              ++input_line_pointer;
-             group_name = obj_elf_section_name ();
-             if (group_name == NULL)
+             match.group_name = obj_elf_section_name ();
+             if (match.group_name == NULL)
                attr &= ~SHF_GROUP;
              else if (*input_line_pointer == ',')
                {
                  ++input_line_pointer;
                  SKIP_WHITESPACE ();
                attr &= ~SHF_GROUP;
              else if (*input_line_pointer == ',')
                {
                  ++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;
                    }
                }
                    {
                      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)
                linkonce = 1;
            }
          else if ((attr & SHF_GROUP) != 0)
@@ -1080,10 +1311,91 @@ obj_elf_section (int push)
              const char *now_group = elf_group_name (now_seg);
              if (now_group != NULL)
                {
              const char *now_group = elf_group_name (now_seg);
              if (now_group != NULL)
                {
-                 group_name = xstrdup (now_group);
+                 match.group_name = xstrdup (now_group);
                  linkonce = (now_seg->flags & SEC_LINK_ONCE) != 0;
                }
            }
                  linkonce = (now_seg->flags & SEC_LINK_ONCE) != 0;
                }
            }
+
+         if ((gnu_attr & SHF_GNU_MBIND) != 0 && *input_line_pointer == ',')
+           {
+             char *save = input_line_pointer;
+             ++input_line_pointer;
+             SKIP_WHITESPACE ();
+             if (ISDIGIT (* input_line_pointer))
+               {
+                 char *t = input_line_pointer;
+                 match.sh_info = strtoul (input_line_pointer,
+                                       &input_line_pointer, 0);
+                 if (match.sh_info == (unsigned int) -1)
+                   {
+                     as_warn (_("unsupported mbind section info: %s"), t);
+                     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 (startswith (input_line_pointer, "unique"))
+               {
+                 input_line_pointer += 6;
+                 SKIP_WHITESPACE ();
+                 if (*input_line_pointer == ',')
+                   {
+                     ++input_line_pointer;
+                     SKIP_WHITESPACE ();
+                     if (ISDIGIT (* input_line_pointer))
+                       {
+                         bfd_vma id;
+                         bool overflow;
+                         char *t = input_line_pointer;
+                         if (sizeof (bfd_vma) <= sizeof (unsigned long))
+                           {
+                             errno = 0;
+                             id = strtoul (input_line_pointer,
+                                           &input_line_pointer, 0);
+                             overflow = (id == (unsigned long) -1
+                                         && errno == ERANGE);
+                           }
+                         else
+                           {
+                             id = bfd_scan_vma
+                               (input_line_pointer,
+                                (const char **) &input_line_pointer, 0);
+                             overflow = id == ~(bfd_vma) 0;
+                           }
+                         if (overflow || id > (unsigned int) -1)
+                           {
+                             char *linefeed, saved_char = 0;
+                             if ((linefeed = strchr (t, '\n')) != NULL)
+                               {
+                                 saved_char = *linefeed;
+                                 *linefeed = '\0';
+                               }
+                             as_bad (_("unsupported section id: %s"), t);
+                             if (saved_char)
+                               *linefeed = saved_char;
+                           }
+                         else
+                           {
+                             match.section_id = id;
+                             match.flags |= SEC_ASSEMBLER_SECTION_ID;
+                           }
+                       }
+                   }
+               }
+             else
+               input_line_pointer = save;
+           }
        }
       else
        {
        }
       else
        {
@@ -1098,11 +1410,12 @@ obj_elf_section (int push)
                  ignore_rest_of_line ();
                  return;
                }
                  ignore_rest_of_line ();
                  return;
                }
-             beg = ++input_line_pointer;
-             c = get_symbol_end ();
-             *input_line_pointer = c;
+             ++input_line_pointer;
+             c = get_symbol_name (& beg);
+             (void) restore_line_pointer (c);
 
 
-             attr |= obj_elf_section_word (beg, input_line_pointer - beg, & type);
+             attr |= obj_elf_section_word (beg, input_line_pointer - beg,
+                                           &type);
 
              SKIP_WHITESPACE ();
            }
 
              SKIP_WHITESPACE ();
            }
@@ -1111,15 +1424,71 @@ obj_elf_section (int push)
        }
     }
 
        }
     }
 
-done:
+ done:
   demand_empty_rest_of_line ();
 
   demand_empty_rest_of_line ();
 
-  obj_elf_change_section (name, type, attr, entsize, group_name, linkonce, push);
+  if ((gnu_attr & (SHF_GNU_MBIND | SHF_GNU_RETAIN)) != 0)
+    {
+      const struct elf_backend_data *bed;
+      bool mbind_p = (gnu_attr & SHF_GNU_MBIND) != 0;
+
+      if (mbind_p && (attr & SHF_ALLOC) == 0)
+       as_bad (_("SHF_ALLOC isn't set for GNU_MBIND section: %s"), name);
+
+      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 ?  */
+    }
 
   if (push && new_subsection != -1)
     subseg_set (now_seg, new_subsection);
 }
 
 
   if (push && new_subsection != -1)
     subseg_set (now_seg, new_subsection);
 }
 
+/* Change to the .bss section.  */
+
+void
+obj_elf_bss (int i ATTRIBUTE_UNUSED)
+{
+  int temp;
+
+#ifdef md_flush_pending_output
+  md_flush_pending_output ();
+#endif
+
+  obj_elf_section_change_hook ();
+
+  temp = get_absolute_expression ();
+  subseg_set (bss_section, (subsegT) temp);
+  demand_empty_rest_of_line ();
+
+#ifdef md_elf_section_change_hook
+  md_elf_section_change_hook ();
+#endif
+}
+
 /* Change to the .data section.  */
 
 void
 /* Change to the .data section.  */
 
 void
@@ -1129,8 +1498,8 @@ obj_elf_data (int i)
   md_flush_pending_output ();
 #endif
 
   md_flush_pending_output ();
 #endif
 
-  previous_section = now_seg;
-  previous_subsection = now_subseg;
+  obj_elf_section_change_hook ();
+
   s_data (i);
 
 #ifdef md_elf_section_change_hook
   s_data (i);
 
 #ifdef md_elf_section_change_hook
@@ -1147,8 +1516,8 @@ obj_elf_text (int i)
   md_flush_pending_output ();
 #endif
 
   md_flush_pending_output ();
 #endif
 
-  previous_section = now_seg;
-  previous_subsection = now_subseg;
+  obj_elf_section_change_hook ();
+
   s_text (i);
 
 #ifdef md_elf_section_change_hook
   s_text (i);
 
 #ifdef md_elf_section_change_hook
@@ -1165,8 +1534,8 @@ obj_elf_struct (int i)
   md_flush_pending_output ();
 #endif
 
   md_flush_pending_output ();
 #endif
 
-  previous_section = now_seg;
-  previous_subsection = now_subseg;
+  obj_elf_section_change_hook ();
+
   s_struct (i);
 
 #ifdef md_elf_section_change_hook
   s_struct (i);
 
 #ifdef md_elf_section_change_hook
@@ -1183,8 +1552,7 @@ obj_elf_subsection (int ignore ATTRIBUTE_UNUSED)
   md_flush_pending_output ();
 #endif
 
   md_flush_pending_output ();
 #endif
 
-  previous_section = now_seg;
-  previous_subsection = now_subseg;
+  obj_elf_section_change_hook ();
 
   temp = get_absolute_expression ();
   subseg_set (now_seg, (subsegT) temp);
 
   temp = get_absolute_expression ();
   subseg_set (now_seg, (subsegT) temp);
@@ -1223,8 +1591,8 @@ obj_elf_previous (int ignore ATTRIBUTE_UNUSED)
 
   new_section = previous_section;
   new_subsection = previous_subsection;
 
   new_section = previous_section;
   new_subsection = previous_subsection;
-  previous_section = now_seg;
-  previous_subsection = now_subseg;
+  obj_elf_section_change_hook ();
+
   subseg_set (new_section, new_subsection);
 
 #ifdef md_elf_section_change_hook
   subseg_set (new_section, new_subsection);
 
 #ifdef md_elf_section_change_hook
@@ -1267,6 +1635,70 @@ obj_elf_line (int ignore ATTRIBUTE_UNUSED)
   demand_empty_rest_of_line ();
 }
 
   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
 /* 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
@@ -1277,9 +1709,12 @@ static void
 obj_elf_symver (int ignore ATTRIBUTE_UNUSED)
 {
   char *name;
 obj_elf_symver (int ignore ATTRIBUTE_UNUSED)
 {
   char *name;
+  const char *sym_name;
   char c;
   char old_lexat;
   symbolS *sym;
   char c;
   char old_lexat;
   symbolS *sym;
+  struct elf_obj_sy *sy_obj;
+  char *p;
 
   sym = get_sym_from_input_line_and_check ();
 
 
   sym = get_sym_from_input_line_and_check ();
 
@@ -1292,42 +1727,65 @@ obj_elf_symver (int ignore ATTRIBUTE_UNUSED)
 
   ++input_line_pointer;
   SKIP_WHITESPACE ();
 
   ++input_line_pointer;
   SKIP_WHITESPACE ();
-  name = input_line_pointer;
 
   /* Temporarily include '@' in symbol names.  */
   old_lexat = lex_type[(unsigned char) '@'];
   lex_type[(unsigned char) '@'] |= LEX_NAME;
 
   /* Temporarily include '@' in symbol names.  */
   old_lexat = lex_type[(unsigned char) '@'];
   lex_type[(unsigned char) '@'] |= LEX_NAME;
-  c = get_symbol_end ();
+  c = get_symbol_name (& name);
   lex_type[(unsigned char) '@'] = old_lexat;
   lex_type[(unsigned char) '@'] = old_lexat;
+  sym_name = S_GET_NAME (sym);
 
 
-  if (symbol_get_obj (sym)->versioned_name == NULL)
+  if (S_IS_COMMON (sym))
     {
     {
-      symbol_get_obj (sym)->versioned_name = xstrdup (name);
+      as_bad (_("`%s' can't be versioned to common symbol '%s'"),
+             name, sym_name);
+      ignore_rest_of_line ();
+      return;
+    }
 
 
-      *input_line_pointer = c;
+  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 (strchr (symbol_get_obj (sym)->versioned_name,
-                 ELF_VER_CHR) == NULL)
-       {
-         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;
-       }
+  sy_obj = symbol_get_obj (sym);
+  if (obj_elf_find_and_add_versioned_name (name, sym_name,
+                                          p, sy_obj) == NULL)
+    {
+      sy_obj->bad_version = true;
+      ignore_rest_of_line ();
+      return;
     }
     }
-  else
+
+  (void) restore_line_pointer (c);
+
+  if (*input_line_pointer == ',')
     {
     {
-      if (strcmp (symbol_get_obj (sym)->versioned_name, name))
+      char *save = input_line_pointer;
+
+      ++input_line_pointer;
+      SKIP_WHITESPACE ();
+      if (startswith (input_line_pointer, "local"))
        {
        {
-         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 += 5;
+         sy_obj->visibility = visibility_local;
        }
        }
-
-      *input_line_pointer = c;
+      else if (startswith (input_line_pointer, "hidden"))
+       {
+         input_line_pointer += 6;
+         sy_obj->visibility = visibility_hidden;
+       }
+      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 ();
     }
 
   demand_empty_rest_of_line ();
@@ -1338,7 +1796,7 @@ obj_elf_symver (int ignore ATTRIBUTE_UNUSED)
    syntax is ".vtable_inherit CHILDNAME, PARENTNAME".  */
 
 struct fix *
    syntax is ".vtable_inherit CHILDNAME, PARENTNAME".  */
 
 struct fix *
-obj_elf_vtable_inherit (int ignore ATTRIBUTE_UNUSED)
+obj_elf_get_vtable_inherit (void)
 {
   char *cname, *pname;
   symbolS *csym, *psym;
 {
   char *cname, *pname;
   symbolS *csym, *psym;
@@ -1347,8 +1805,7 @@ obj_elf_vtable_inherit (int ignore ATTRIBUTE_UNUSED)
   if (*input_line_pointer == '#')
     ++input_line_pointer;
 
   if (*input_line_pointer == '#')
     ++input_line_pointer;
 
-  cname = input_line_pointer;
-  c = get_symbol_end ();
+  c = get_symbol_name (& cname);
   csym = symbol_find (cname);
 
   /* GCFIXME: should check that we don't have two .vtable_inherits for
   csym = symbol_find (cname);
 
   /* GCFIXME: should check that we don't have two .vtable_inherits for
@@ -1364,7 +1821,7 @@ obj_elf_vtable_inherit (int ignore ATTRIBUTE_UNUSED)
 
   *input_line_pointer = c;
 
 
   *input_line_pointer = c;
 
-  SKIP_WHITESPACE ();
+  SKIP_WHITESPACE_AFTER_NAME ();
   if (*input_line_pointer != ',')
     {
       as_bad (_("expected comma after name in .vtable_inherit"));
   if (*input_line_pointer != ',')
     {
       as_bad (_("expected comma after name in .vtable_inherit"));
@@ -1387,10 +1844,9 @@ obj_elf_vtable_inherit (int ignore ATTRIBUTE_UNUSED)
     }
   else
     {
     }
   else
     {
-      pname = input_line_pointer;
-      c = get_symbol_end ();
+      c = get_symbol_name (& pname);
       psym = symbol_find_or_make (pname);
       psym = symbol_find_or_make (pname);
-      *input_line_pointer = c;
+      restore_line_pointer (c);
     }
 
   demand_empty_rest_of_line ();
     }
 
   demand_empty_rest_of_line ();
@@ -1404,12 +1860,21 @@ obj_elf_vtable_inherit (int ignore ATTRIBUTE_UNUSED)
                  0, psym, 0, 0, BFD_RELOC_VTABLE_INHERIT);
 }
 
                  0, psym, 0, 0, BFD_RELOC_VTABLE_INHERIT);
 }
 
+/* This is a version of obj_elf_get_vtable_inherit() that is
+   suitable for use in struct _pseudo_type tables.  */
+
+void
+obj_elf_vtable_inherit (int ignore ATTRIBUTE_UNUSED)
+{
+  (void) obj_elf_get_vtable_inherit ();
+}
+
 /* This handles the .vtable_entry pseudo-op, which is used to indicate
    to the linker that a vtable slot was used.  The syntax is
    ".vtable_entry tablename, offset".  */
 
 struct fix *
 /* This handles the .vtable_entry pseudo-op, which is used to indicate
    to the linker that a vtable slot was used.  The syntax is
    ".vtable_entry tablename, offset".  */
 
 struct fix *
-obj_elf_vtable_entry (int ignore ATTRIBUTE_UNUSED)
+obj_elf_get_vtable_entry (void)
 {
   symbolS *sym;
   offsetT offset;
 {
   symbolS *sym;
   offsetT offset;
@@ -1437,6 +1902,204 @@ obj_elf_vtable_entry (int ignore ATTRIBUTE_UNUSED)
                  BFD_RELOC_VTABLE_ENTRY);
 }
 
                  BFD_RELOC_VTABLE_ENTRY);
 }
 
+/* This is a version of obj_elf_get_vtable_entry() that is
+   suitable for use in struct _pseudo_type tables.  */
+
+void
+obj_elf_vtable_entry (int ignore ATTRIBUTE_UNUSED)
+{
+  (void) obj_elf_get_vtable_entry ();
+}
+
+#define skip_whitespace(str)  do { if (*(str) == ' ') ++(str); } while (0)
+
+static inline int
+skip_past_char (char ** str, char c)
+{
+  if (**str == c)
+    {
+      (*str)++;
+      return 0;
+    }
+  else
+    return -1;
+}
+#define skip_past_comma(str) skip_past_char (str, ',')
+
+/* A list of attributes that have been explicitly set by the assembly code.
+   VENDOR is the vendor id, BASE is the tag shifted right by the number
+   of bits in MASK, and bit N of MASK is set if tag BASE+N has been set.  */
+struct recorded_attribute_info {
+  struct recorded_attribute_info *next;
+  int vendor;
+  unsigned int base;
+  unsigned long mask;
+};
+static struct recorded_attribute_info *recorded_attributes;
+
+/* Record that we have seen an explicit specification of attribute TAG
+   for vendor VENDOR.  */
+
+static void
+record_attribute (int vendor, unsigned int tag)
+{
+  unsigned int base;
+  unsigned long mask;
+  struct recorded_attribute_info *rai;
+
+  base = tag / (8 * sizeof (rai->mask));
+  mask = 1UL << (tag % (8 * sizeof (rai->mask)));
+  for (rai = recorded_attributes; rai; rai = rai->next)
+    if (rai->vendor == vendor && rai->base == base)
+      {
+       rai->mask |= mask;
+       return;
+      }
+
+  rai = XNEW (struct recorded_attribute_info);
+  rai->next = recorded_attributes;
+  rai->vendor = vendor;
+  rai->base = base;
+  rai->mask = mask;
+  recorded_attributes = rai;
+}
+
+/* Return true if we have seen an explicit specification of attribute TAG
+   for vendor VENDOR.  */
+
+bool
+obj_elf_seen_attribute (int vendor, unsigned int tag)
+{
+  unsigned int base;
+  unsigned long mask;
+  struct recorded_attribute_info *rai;
+
+  base = tag / (8 * sizeof (rai->mask));
+  mask = 1UL << (tag % (8 * sizeof (rai->mask)));
+  for (rai = recorded_attributes; rai; rai = rai->next)
+    if (rai->vendor == vendor && rai->base == base)
+      return (rai->mask & mask) != 0;
+  return false;
+}
+
+/* Parse an attribute directive for VENDOR.
+   Returns the attribute number read, or zero on error.  */
+
+int
+obj_elf_vendor_attribute (int vendor)
+{
+  expressionS exp;
+  int type;
+  int tag;
+  unsigned int i = 0;
+  char *s = NULL;
+
+  /* Read the first number or name.  */
+  skip_whitespace (input_line_pointer);
+  s = input_line_pointer;
+  if (ISDIGIT (*input_line_pointer))
+    {
+      expression (& exp);
+      if (exp.X_op != O_constant)
+       goto bad;
+      tag = exp.X_add_number;
+    }
+  else
+    {
+      char *name;
+
+      /* A name may contain '_', but no other punctuation.  */
+      for (; ISALNUM (*input_line_pointer) || *input_line_pointer == '_';
+          ++input_line_pointer)
+       i++;
+      if (i == 0)
+       goto bad;
+
+      name = xstrndup (s, i);
+
+#ifndef CONVERT_SYMBOLIC_ATTRIBUTE
+#define CONVERT_SYMBOLIC_ATTRIBUTE(a) -1
+#endif
+
+      tag = CONVERT_SYMBOLIC_ATTRIBUTE (name);
+      if (tag == -1)
+       {
+         as_bad (_("Attribute name not recognised: %s"), name);
+         ignore_rest_of_line ();
+         free (name);
+         return 0;
+       }
+      free (name);
+    }
+
+  type = _bfd_elf_obj_attrs_arg_type (stdoutput, vendor, tag);
+
+  if (skip_past_comma (&input_line_pointer) == -1)
+    goto bad;
+  if (type & 1)
+    {
+      expression (& exp);
+      if (exp.X_op != O_constant)
+       {
+         as_bad (_("expected numeric constant"));
+         ignore_rest_of_line ();
+         return 0;
+       }
+      i = exp.X_add_number;
+    }
+  if ((type & 3) == 3
+      && skip_past_comma (&input_line_pointer) == -1)
+    {
+      as_bad (_("expected comma"));
+      ignore_rest_of_line ();
+      return 0;
+    }
+  if (type & 2)
+    {
+      int len;
+
+      skip_whitespace (input_line_pointer);
+      if (*input_line_pointer != '"')
+       goto bad_string;
+      s = demand_copy_C_string (&len);
+    }
+
+  record_attribute (vendor, tag);
+  switch (type & 3)
+    {
+    case 3:
+      bfd_elf_add_obj_attr_int_string (stdoutput, vendor, tag, i, s);
+      break;
+    case 2:
+      bfd_elf_add_obj_attr_string (stdoutput, vendor, tag, s);
+      break;
+    case 1:
+      bfd_elf_add_obj_attr_int (stdoutput, vendor, tag, i);
+      break;
+    default:
+      abort ();
+    }
+
+  demand_empty_rest_of_line ();
+  return tag;
+ bad_string:
+  as_bad (_("bad string constant"));
+  ignore_rest_of_line ();
+  return 0;
+ bad:
+  as_bad (_("expected <tag> , <value>"));
+  ignore_rest_of_line ();
+  return 0;
+}
+
+/* Parse a .gnu_attribute directive.  */
+
+static void
+obj_elf_gnu_attribute (int ignored ATTRIBUTE_UNUSED)
+{
+  obj_elf_vendor_attribute (OBJ_ATTR_GNU);
+}
+
 void
 elf_obj_read_begin_hook (void)
 {
 void
 elf_obj_read_begin_hook (void)
 {
@@ -1461,6 +2124,22 @@ elf_obj_symbol_new_hook (symbolS *symbolP)
 #endif
 }
 
 #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.  */
 /* 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.  */
@@ -1473,13 +2152,12 @@ elf_copy_symbol_attributes (symbolS *dest, symbolS *src)
   if (srcelf->size)
     {
       if (destelf->size == NULL)
   if (srcelf->size)
     {
       if (destelf->size == NULL)
-       destelf->size = (expressionS *) xmalloc (sizeof (expressionS));
+       destelf->size = XNEW (expressionS);
       *destelf->size = *srcelf->size;
     }
   else
     {
       *destelf->size = *srcelf->size;
     }
   else
     {
-      if (destelf->size != NULL)
-       free (destelf->size);
+      free (destelf->size);
       destelf->size = NULL;
     }
   S_SET_SIZE (dest, S_GET_SIZE (src));
       destelf->size = NULL;
     }
   S_SET_SIZE (dest, S_GET_SIZE (src));
@@ -1517,9 +2195,8 @@ obj_elf_version (int ignore ATTRIBUTE_UNUSED)
 
       /* Create the .note section.  */
       note_secp = subseg_new (".note", 0);
 
       /* Create the .note section.  */
       note_secp = subseg_new (".note", 0);
-      bfd_set_section_flags (stdoutput,
-                            note_secp,
-                            SEC_HAS_CONTENTS | SEC_READONLY);
+      bfd_set_section_flags (note_secp, SEC_HAS_CONTENTS | SEC_READONLY);
+      record_alignment (note_secp, 2);
 
       /* Process the version string.  */
       len = strlen (name) + 1;
 
       /* Process the version string.  */
       len = strlen (name) + 1;
@@ -1551,15 +2228,15 @@ obj_elf_version (int ignore ATTRIBUTE_UNUSED)
 static void
 obj_elf_size (int ignore ATTRIBUTE_UNUSED)
 {
 static void
 obj_elf_size (int ignore ATTRIBUTE_UNUSED)
 {
-  char *name = input_line_pointer;
-  char c = get_symbol_end ();
+  char *name;
+  char c = get_symbol_name (&name);
   char *p;
   expressionS exp;
   symbolS *sym;
 
   p = input_line_pointer;
   *p = c;
   char *p;
   expressionS exp;
   symbolS *sym;
 
   p = input_line_pointer;
   *p = c;
-  SKIP_WHITESPACE ();
+  SKIP_WHITESPACE_AFTER_NAME ();
   if (*input_line_pointer != ',')
     {
       *p = 0;
   if (*input_line_pointer != ',')
     {
       *p = 0;
@@ -1582,16 +2259,12 @@ obj_elf_size (int ignore ATTRIBUTE_UNUSED)
   if (exp.X_op == O_constant)
     {
       S_SET_SIZE (sym, exp.X_add_number);
   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
     {
     }
   else
     {
-      symbol_get_obj (sym)->size =
-          (expressionS *) xmalloc (sizeof (expressionS));
+      symbol_get_obj (sym)->size = XNEW (expressionS);
       *symbol_get_obj (sym)->size = exp;
     }
   demand_empty_rest_of_line ();
       *symbol_get_obj (sym)->size = exp;
     }
   demand_empty_rest_of_line ();
@@ -1631,7 +2304,7 @@ obj_elf_type_name (char *cp)
       *input_line_pointer = '\0';
     }
   else
       *input_line_pointer = '\0';
     }
   else
-    *cp = get_symbol_end ();
+    *cp = get_symbol_name (&p);
 
   return p;
 }
 
   return p;
 }
@@ -1713,27 +2386,29 @@ obj_elf_type (int ignore ATTRIBUTE_UNUSED)
       const struct elf_backend_data *bed;
 
       bed = get_elf_backend_data (stdoutput);
       const struct elf_backend_data *bed;
 
       bed = get_elf_backend_data (stdoutput);
-      if (!(bed->elf_osabi == ELFOSABI_GNU
-           || bed->elf_osabi == ELFOSABI_FREEBSD
-           /* GNU is still using the default value 0.  */
-           || bed->elf_osabi == ELFOSABI_NONE))
-       as_bad (_("symbol type \"%s\" is supported only by GNU and FreeBSD targets"),
-               type_name);
+      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)
     {
       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_GNU
-           /* GNU is still using the default value 0.  */
-           || bed->elf_osabi == ELFOSABI_NONE))
+      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);
        as_bad (_("symbol type \"%s\" is supported only by GNU targets"),
                type_name);
+      elf_tdata (stdoutput)->has_gnu_osabi |= elf_gnu_osabi_unique;
       type = BSF_OBJECT | BSF_GNU_UNIQUE;
       type = BSF_OBJECT | BSF_GNU_UNIQUE;
-      /* PR 10549: Always set OSABI field to GNU for objects containing unique symbols.  */
-      bed->elf_osabi = ELFOSABI_GNU;
     }
 #ifdef md_elf_symbol_type
   else if ((type = md_elf_symbol_type (type_name, sym, elfsym)) != -1)
     }
 #ifdef md_elf_symbol_type
   else if ((type = md_elf_symbol_type (type_name, sym, elfsym)) != -1)
@@ -1747,7 +2422,38 @@ obj_elf_type (int ignore ATTRIBUTE_UNUSED)
   if (*input_line_pointer == '"')
     ++input_line_pointer;
 
   if (*input_line_pointer == '"')
     ++input_line_pointer;
 
-  elfsym->symbol.flags |= type;
+#ifdef md_elf_symbol_type_change
+  if (!md_elf_symbol_type_change (sym, elfsym, type))
+#endif
+    {
+      flagword mask = BSF_FUNCTION | BSF_OBJECT;
+
+      if (type != BSF_FUNCTION)
+       mask |= BSF_GNU_INDIRECT_FUNCTION;
+      if (type != BSF_OBJECT)
+       {
+         mask |= BSF_GNU_UNIQUE | BSF_THREAD_LOCAL;
+
+         if (S_IS_COMMON (sym))
+           {
+             as_bad (_("cannot change type of common symbol '%s'"),
+                     S_GET_NAME (sym));
+             mask = type = 0;
+           }
+       }
+
+      /* Don't warn when changing to STT_NOTYPE.  */
+      if (type)
+       {
+         flagword new = (elfsym->symbol.flags & ~mask) | type;
+
+         if (new != (elfsym->symbol.flags | type))
+           as_warn (_("symbol '%s' already has its type set"), S_GET_NAME (sym));
+         elfsym->symbol.flags = new;
+       }
+      else
+       elfsym->symbol.flags &= ~mask;
+    }
 
   demand_empty_rest_of_line ();
 }
 
   demand_empty_rest_of_line ();
 }
@@ -1767,9 +2473,8 @@ obj_elf_ident (int ignore ATTRIBUTE_UNUSED)
     {
       char *p;
       comment_section = subseg_new (".comment", 0);
     {
       char *p;
       comment_section = subseg_new (".comment", 0);
-      bfd_set_section_flags (stdoutput, comment_section,
-                            SEC_READONLY | SEC_HAS_CONTENTS
-                            | SEC_MERGE | SEC_STRINGS);
+      bfd_set_section_flags (comment_section, (SEC_READONLY | SEC_HAS_CONTENTS
+                                              | SEC_MERGE | SEC_STRINGS));
       comment_section->entsize = 1;
 #ifdef md_elf_section_change_hook
       md_elf_section_change_hook ();
       comment_section->entsize = 1;
 #ifdef md_elf_section_change_hook
       md_elf_section_change_hook ();
@@ -1790,27 +2495,26 @@ obj_elf_ident (int ignore ATTRIBUTE_UNUSED)
 void
 obj_elf_init_stab_section (segT seg)
 {
 void
 obj_elf_init_stab_section (segT seg)
 {
-  char *file;
+  const char *file;
   char *p;
   char *stabstr_name;
   unsigned int stroff;
 
   /* Force the section to align to a longword boundary.  Without this,
      UnixWare ar crashes.  */
   char *p;
   char *stabstr_name;
   unsigned int stroff;
 
   /* Force the section to align to a longword boundary.  Without this,
      UnixWare ar crashes.  */
-  bfd_set_section_alignment (stdoutput, seg, 2);
+  bfd_set_section_alignment (seg, 2);
 
   /* Make space for this first symbol.  */
   p = frag_more (12);
   /* Zero it out.  */
   memset (p, 0, 12);
 
   /* Make space for this first symbol.  */
   p = frag_more (12);
   /* Zero it out.  */
   memset (p, 0, 12);
-  as_where (&file, NULL);
-  stabstr_name = (char *) xmalloc (strlen (segment_name (seg)) + 4);
-  strcpy (stabstr_name, segment_name (seg));
-  strcat (stabstr_name, "str");
-  stroff = get_stab_string_offset (file, stabstr_name);
+  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);
   know (stroff == 1 || (stroff == 0 && file[0] == '\0'));
   md_number_to_chars (p, stroff, 4);
   seg_info (seg)->stabu.p = p;
   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
 }
 
 #endif
@@ -1825,26 +2529,25 @@ adjust_stab_sections (bfd *abfd, asection *sec, void *xxx ATTRIBUTE_UNUSED)
   char *p;
   int strsz, nsyms;
 
   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;
 
     return;
   if (!strcmp ("str", sec->name + strlen (sec->name) - 3))
     return;
 
-  name = (char *) alloca (strlen (sec->name) + 4);
-  strcpy (name, sec->name);
-  strcat (name, "str");
+  name = concat (sec->name, "str", NULL);
   strsec = bfd_get_section_by_name (abfd, name);
   if (strsec)
   strsec = bfd_get_section_by_name (abfd, name);
   if (strsec)
-    strsz = bfd_section_size (abfd, strsec);
+    strsz = bfd_section_size (strsec);
   else
     strsz = 0;
   else
     strsz = 0;
-  nsyms = bfd_section_size (abfd, sec) / 12 - 1;
+  nsyms = bfd_section_size (sec) / 12 - 1;
 
   p = seg_info (sec)->stabu.p;
   gas_assert (p != 0);
 
   bfd_h_put_16 (abfd, nsyms, p + 6);
   bfd_h_put_32 (abfd, strsz, p + 8);
 
   p = seg_info (sec)->stabu.p;
   gas_assert (p != 0);
 
   bfd_h_put_16 (abfd, nsyms, p + 6);
   bfd_h_put_32 (abfd, strsz, p + 8);
+  free (name);
 }
 
 #ifdef NEED_ECOFF_DEBUG
 }
 
 #ifdef NEED_ECOFF_DEBUG
@@ -1868,13 +2571,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.  */
 
    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)
 elf_get_extr (asymbol *sym, EXTR *ext)
 {
   if (sym->udata.p == NULL)
-    return FALSE;
+    return false;
   *ext = *(EXTR *) sym->udata.p;
   *ext = *(EXTR *) sym->udata.p;
-  return TRUE;
+  return true;
 }
 
 /* This function is called by bfd_ecoff_debug_externals.  It has
 }
 
 /* This function is called by bfd_ecoff_debug_externals.  It has
@@ -1893,6 +2596,7 @@ elf_frob_symbol (symbolS *symp, int *puntp)
 {
   struct elf_obj_sy *sy_obj;
   expressionS *size;
 {
   struct elf_obj_sy *sy_obj;
   expressionS *size;
+  struct elf_versioned_name_list *versioned_name;
 
 #ifdef NEED_ECOFF_DEBUG
   if (ECOFF_DEBUGGING)
 
 #ifdef NEED_ECOFF_DEBUG
   if (ECOFF_DEBUGGING)
@@ -1909,7 +2613,7 @@ elf_frob_symbol (symbolS *symp, int *puntp)
        S_SET_SIZE (symp, size->X_add_number);
       else
        {
        S_SET_SIZE (symp, size->X_add_number);
       else
        {
-         if (flag_size_check == size_check_error)
+         if (!flag_allow_nonconst_size)
            as_bad (_(".size expression for %s "
                      "does not evaluate to a constant"), S_GET_NAME (symp));
          else
            as_bad (_(".size expression for %s "
                      "does not evaluate to a constant"), S_GET_NAME (symp));
          else
@@ -1920,63 +2624,47 @@ elf_frob_symbol (symbolS *symp, int *puntp)
       sy_obj->size = NULL;
     }
 
       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);
-      know (p != NULL);
-
       /* This symbol was given a new name with the .symver directive.
       /* 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
         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.  */
-
-      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
        {
        }
       else
        {
-         if (p[1] == ELF_VER_CHR && p[2] == ELF_VER_CHR)
+         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)
            {
            {
-             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
-           {
-             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.  */
+             symbolS *symp2 = symbol_find_or_make (versioned_name->name);
 
              S_SET_SEGMENT (symp2, S_GET_SEGMENT (symp));
 
 
              S_SET_SEGMENT (symp2, S_GET_SEGMENT (symp));
 
@@ -1984,7 +2672,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)
                 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));
 
 
              symbol_set_frag (symp2, symbol_get_frag (symp));
 
@@ -1999,6 +2688,24 @@ elf_frob_symbol (symbolS *symp, int *puntp)
              if (S_IS_EXTERNAL (symp))
                S_SET_EXTERNAL (symp2);
            }
              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;
+           }
        }
     }
 
        }
     }
 
@@ -2009,40 +2716,26 @@ elf_frob_symbol (symbolS *symp, int *puntp)
        as_bad (_("symbol `%s' can not be both weak and common"),
                S_GET_NAME (symp));
     }
        as_bad (_("symbol `%s' can not be both weak and common"),
                S_GET_NAME (symp));
     }
-
-#ifdef TC_MIPS
-  /* The Irix 5 and 6 assemblers set the type of any common symbol and
-     any undefined non-function symbol to STT_OBJECT.  We try to be
-     compatible, since newer Irix 5 and 6 linkers care.  However, we
-     only set undefined symbols to be STT_OBJECT if we are on Irix,
-     because that is the only time gcc will generate the necessary
-     .global directives to mark functions.  */
-
-  if (S_IS_COMMON (symp))
-    symbol_get_bfdsym (symp)->flags |= BSF_OBJECT;
-
-  if (strstr (TARGET_OS, "irix") != NULL
-      && ! S_IS_DEFINED (symp)
-      && (symbol_get_bfdsym (symp)->flags & BSF_FUNCTION) == 0)
-    symbol_get_bfdsym (symp)->flags |= BSF_OBJECT;
-#endif
 }
 
 struct group_list
 {
   asection **head;             /* Section lists.  */
 }
 
 struct group_list
 {
   asection **head;             /* Section lists.  */
-  unsigned int *elt_count;     /* Number of sections in each list.  */
   unsigned int num_group;      /* Number of 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;
+
 /* Called via bfd_map_over_sections.  If SEC is a member of a group,
    add it to a list of sections belonging to the group.  INF is a
    pointer to a struct group_list, which is where we store the head of
 /* Called via bfd_map_over_sections.  If SEC is a member of a group,
    add it to a list of sections belonging to the group.  INF is a
    pointer to a struct group_list, which is where we store the head of
-   each list.  */
+   each list.  If its link_to_symbol_name isn't NULL, set up its
+   linked-to section.  */
 
 static void
 
 static void
-build_group_lists (bfd *abfd ATTRIBUTE_UNUSED, asection *sec, void *inf)
+build_additional_section_info (bfd *abfd ATTRIBUTE_UNUSED,
+                                 asection *sec, void *inf)
 {
   struct group_list *list = (struct group_list *) inf;
   const char *group_name = elf_group_name (sec);
 {
   struct group_list *list = (struct group_list *) inf;
   const char *group_name = elf_group_name (sec);
@@ -2050,17 +2743,28 @@ build_group_lists (bfd *abfd ATTRIBUTE_UNUSED, asection *sec, void *inf)
   unsigned int *elem_idx;
   unsigned int *idx_ptr;
 
   unsigned int *elem_idx;
   unsigned int *idx_ptr;
 
+  if (sec->map_head.linked_to_symbol_name)
+    {
+      symbolS *linked_to_sym;
+      linked_to_sym = symbol_find (sec->map_head.linked_to_symbol_name);
+      if (!linked_to_sym || !S_IS_DEFINED (linked_to_sym))
+       as_bad (_("undefined linked-to symbol `%s' on section `%s'"),
+               sec->map_head.linked_to_symbol_name,
+               bfd_section_name (sec));
+      else
+       elf_linked_to_section (sec) = S_GET_SEGMENT (linked_to_sym);
+    }
+
   if (group_name == NULL)
     return;
 
   /* If this group already has a list, add the section to the head of
      the list.  */
   if (group_name == NULL)
     return;
 
   /* 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];
       list->head[*elem_idx] = sec;
   if (elem_idx != NULL)
     {
       elf_next_in_group (sec) = list->head[*elem_idx];
       list->head[*elem_idx] = sec;
-      list->elt_count[*elem_idx] += 1;
       return;
     }
 
       return;
     }
 
@@ -2070,58 +2774,57 @@ build_group_lists (bfd *abfd ATTRIBUTE_UNUSED, asection *sec, void *inf)
   if ((i & 127) == 0)
     {
       unsigned int newsize = i + 128;
   if ((i & 127) == 0)
     {
       unsigned int newsize = i + 128;
-      list->head = (asection **) xrealloc (list->head,
-                                           newsize * sizeof (*list->head));
-      list->elt_count = (unsigned int *)
-          xrealloc (list->elt_count, newsize * sizeof (*list->elt_count));
+      list->head = XRESIZEVEC (asection *, list->head, newsize);
     }
   list->head[i] = sec;
     }
   list->head[i] = sec;
-  list->elt_count[i] = 1;
   list->num_group += 1;
 
   /* Add index to hash.  */
   list->num_group += 1;
 
   /* Add index to hash.  */
-  idx_ptr = (unsigned int *) xmalloc (sizeof (unsigned int));
+  idx_ptr = XNEW (unsigned int);
   *idx_ptr = i;
   *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.  */
+
 void
 elf_adjust_symtab (void)
 {
 void
 elf_adjust_symtab (void)
 {
-  struct group_list list;
   unsigned int i;
 
   /* Go find section groups.  */
   unsigned int i;
 
   /* Go find section groups.  */
-  list.num_group = 0;
-  list.head = NULL;
-  list.elt_count = NULL;
-  list.indexes = hash_new ();
-  bfd_map_over_sections (stdoutput, build_group_lists, &list);
-  
+  groups.num_group = 0;
+  groups.head = NULL;
+  groups.indexes = str_htab_create ();
+  bfd_map_over_sections (stdoutput, build_additional_section_info,
+                        &groups);
+
   /* Make the SHT_GROUP sections that describe each section group.  We
      can't set up the section contents here yet, because elf section
      indices have yet to be calculated.  elf.c:set_group_contents does
      the rest of the work.  */
   /* Make the SHT_GROUP sections that describe each section group.  We
      can't set up the section contents here yet, because elf section
      indices have yet to be calculated.  elf.c:set_group_contents does
      the rest of the work.  */
- for (i = 0; i < list.num_group; i++)
+ for (i = 0; i < groups.num_group; i++)
     {
     {
-      const char *group_name = elf_group_name (list.head[i]);
+      const char *group_name = elf_group_name (groups.head[i]);
       const char *sec_name;
       asection *s;
       flagword flags;
       struct symbol *sy;
       const char *sec_name;
       asection *s;
       flagword flags;
       struct symbol *sy;
-      bfd_size_type size;
 
       flags = SEC_READONLY | SEC_HAS_CONTENTS | SEC_IN_MEMORY | SEC_GROUP;
 
       flags = SEC_READONLY | SEC_HAS_CONTENTS | SEC_IN_MEMORY | SEC_GROUP;
-      for (s = list.head[i]; s != NULL; s = elf_next_in_group (s))
+      for (s = groups.head[i]; s != NULL; s = elf_next_in_group (s))
        if ((s->flags ^ flags) & SEC_LINK_ONCE)
          {
            flags |= SEC_LINK_ONCE | SEC_LINK_DUPLICATES_DISCARD;
        if ((s->flags ^ flags) & SEC_LINK_ONCE)
          {
            flags |= SEC_LINK_ONCE | SEC_LINK_DUPLICATES_DISCARD;
-           if (s != list.head[i])
+           if (s != groups.head[i])
              {
                as_warn (_("assuming all members of group `%s' are COMDAT"),
                         group_name);
              {
                as_warn (_("assuming all members of group `%s' are COMDAT"),
                         group_name);
@@ -2132,8 +2835,8 @@ elf_adjust_symtab (void)
       sec_name = ".group";
       s = subseg_force_new (sec_name, 0);
       if (s == NULL
       sec_name = ".group";
       s = subseg_force_new (sec_name, 0);
       if (s == NULL
-         || !bfd_set_section_flags (stdoutput, s, flags)
-         || !bfd_set_section_alignment (stdoutput, s, 2))
+         || !bfd_set_section_flags (s, flags)
+         || !bfd_set_section_alignment (s, 2))
        {
          as_fatal (_("can't create group: %s"),
                    bfd_errmsg (bfd_get_error ()));
        {
          as_fatal (_("can't create group: %s"),
                    bfd_errmsg (bfd_get_error ()));
@@ -2141,17 +2844,15 @@ elf_adjust_symtab (void)
       elf_section_type (s) = SHT_GROUP;
 
       /* Pass a pointer to the first section in this group.  */
       elf_section_type (s) = SHT_GROUP;
 
       /* Pass a pointer to the first section in this group.  */
-      elf_next_in_group (s) = list.head[i];
+      elf_next_in_group (s) = groups.head[i];
+      elf_sec_group (groups.head[i]) = s;
       /* Make sure that the signature symbol for the group has the
         name of the group.  */
       sy = symbol_find_exact (group_name);
       /* Make sure that the signature symbol for the group has the
         name of the group.  */
       sy = symbol_find_exact (group_name);
-      if (!sy
-         || (sy != symbol_lastP
-             && (sy->sy_next == NULL
-                 || sy->sy_next->sy_previous != sy)))
+      if (!sy || !symbol_on_chain (sy, symbol_rootP, symbol_lastP))
        {
          /* Create the symbol now.  */
        {
          /* 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.  */
 #ifdef TE_SOLARIS
          /* Before Solaris 11 build 154, Sun ld rejects local group
             signature symbols, so make them weak hidden instead.  */
@@ -2163,17 +2864,10 @@ elf_adjust_symtab (void)
          symbol_table_insert (sy);
        }
       elf_group_id (s) = symbol_get_bfdsym (sy);
          symbol_table_insert (sy);
        }
       elf_group_id (s) = symbol_get_bfdsym (sy);
-
-      size = 4 * (list.elt_count[i] + 1);
-      bfd_set_section_size (stdoutput, s, size);
-      s->contents = (unsigned char *) frag_more (size);
-      frag_now->fr_fix = frag_now_fix_octets ();
-      frag_wane (frag_now);
+      /* Mark the group signature symbol as used so that it will be
+        included in the symbol table.  */
+      symbol_mark_used_in_reloc (sy);
     }
     }
-
-  /* Cleanup hash.  */
-  hash_traverse (list.indexes, free_section_idx);
-  hash_die (list.indexes);
 }
 
 void
 }
 
 void
@@ -2196,37 +2890,61 @@ elf_frob_file_before_adjust (void)
       symbolS *symp;
 
       for (symp = symbol_rootP; symp; symp = symbol_next (symp))
       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);
-               know (p != NULL);
-               if (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);
+       }
     }
 }
 
     }
 }
 
@@ -2238,6 +2956,31 @@ elf_frob_file_before_adjust (void)
 void
 elf_frob_file_after_relocs (void)
 {
 void
 elf_frob_file_after_relocs (void)
 {
+  unsigned int i;
+
+  /* Set SHT_GROUP section size.  */
+  for (i = 0; i < groups.num_group; i++)
+    {
+      asection *s, *head, *group;
+      bfd_size_type size;
+
+      head = groups.head[i];
+      size = 4;
+      for (s = head; s != NULL; s = elf_next_in_group (s))
+       size += (s->flags & SEC_RELOC) != 0 ? 8 : 4;
+
+      group = elf_sec_group (head);
+      subseg_set (group, 0);
+      bfd_set_section_size (group, size);
+      group->contents = (unsigned char *) frag_more (size);
+      frag_now->fr_fix = frag_now_fix_octets ();
+      frag_wane (frag_now);
+    }
+
+  /* Cleanup hash.  */
+  htab_traverse (groups.indexes, free_section_idx, NULL);
+  htab_delete (groups.indexes);
+
 #ifdef NEED_ECOFF_DEBUG
   if (ECOFF_DEBUGGING)
     /* Generate the ECOFF debugging information.  */
 #ifdef NEED_ECOFF_DEBUG
   if (ECOFF_DEBUGGING)
     /* Generate the ECOFF debugging information.  */
@@ -2272,7 +3015,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;
       /* 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 ()));
                                       elf_get_extr, elf_set_index))
        as_fatal (_("failed to set up debugging information: %s"),
                  bfd_errmsg (bfd_get_error ()));
@@ -2286,8 +3029,8 @@ elf_frob_file_after_relocs (void)
         to force the ELF backend to allocate a file position, and then
         write out the data.  FIXME: Is this really the best way to do
         this?  */
         to force the ELF backend to allocate a file position, and then
         write out the data.  FIXME: Is this really the best way to do
         this?  */
-      bfd_set_section_size
-       (stdoutput, sec, bfd_ecoff_debug_size (stdoutput, &debug, debug_swap));
+      bfd_set_section_size (sec, bfd_ecoff_debug_size (stdoutput, &debug,
+                                                      debug_swap));
 
       /* Pass BUF to bfd_set_section_contents because this will
         eventually become a call to fwrite, and ISO C prohibits
 
       /* Pass BUF to bfd_set_section_contents because this will
         eventually become a call to fwrite, and ISO C prohibits
@@ -2308,103 +3051,6 @@ elf_frob_file_after_relocs (void)
 #endif /* NEED_ECOFF_DEBUG */
 }
 
 #endif /* NEED_ECOFF_DEBUG */
 }
 
-#ifdef SCO_ELF
-
-/* Heavily plagiarized from obj_elf_version.  The idea is to emit the
-   SCO specific identifier in the .notes section to satisfy the SCO
-   linker.
-
-   This looks more complicated than it really is.  As opposed to the
-   "obvious" solution, this should handle the cross dev cases
-   correctly.  (i.e, hosting on a 64 bit big endian processor, but
-   generating SCO Elf code) Efficiency isn't a concern, as there
-   should be exactly one of these sections per object module.
-
-   SCO OpenServer 5 identifies it's ELF modules with a standard ELF
-   .note section.
-
-   int_32 namesz  = 4 ;  Name size
-   int_32 descsz  = 12 ; Descriptive information
-   int_32 type    = 1 ;
-   char   name[4] = "SCO" ; Originator name ALWAYS SCO + NULL
-   int_32 version = (major ver # << 16)  | version of tools ;
-   int_32 source  = (tool_id << 16 ) | 1 ;
-   int_32 info    = 0 ;    These are set by the SCO tools, but we
-                          don't know enough about the source
-                          environment to set them.  SCO ld currently
-                          ignores them, and recommends we set them
-                          to zero.  */
-
-#define SCO_MAJOR_VERSION 0x1
-#define SCO_MINOR_VERSION 0x1
-
-void
-sco_id (void)
-{
-
-  char *name;
-  unsigned int c;
-  char ch;
-  char *p;
-  asection *seg = now_seg;
-  subsegT subseg = now_subseg;
-  Elf_Internal_Note i_note;
-  Elf_External_Note e_note;
-  asection *note_secp = NULL;
-  int i, len;
-
-  /* create the .note section */
-
-  note_secp = subseg_new (".note", 0);
-  bfd_set_section_flags (stdoutput,
-                        note_secp,
-                        SEC_HAS_CONTENTS | SEC_READONLY);
-
-  /* process the version string */
-
-  i_note.namesz = 4;
-  i_note.descsz = 12;          /* 12 descriptive bytes */
-  i_note.type = NT_VERSION;    /* Contains a version string */
-
-  p = frag_more (sizeof (i_note.namesz));
-  md_number_to_chars (p, i_note.namesz, 4);
-
-  p = frag_more (sizeof (i_note.descsz));
-  md_number_to_chars (p, i_note.descsz, 4);
-
-  p = frag_more (sizeof (i_note.type));
-  md_number_to_chars (p, i_note.type, 4);
-
-  p = frag_more (4);
-  strcpy (p, "SCO");
-
-  /* Note: this is the version number of the ELF we're representing */
-  p = frag_more (4);
-  md_number_to_chars (p, (SCO_MAJOR_VERSION << 16) | (SCO_MINOR_VERSION), 4);
-
-  /* Here, we pick a magic number for ourselves (yes, I "registered"
-     it with SCO.  The bottom bit shows that we are compat with the
-     SCO ABI.  */
-  p = frag_more (4);
-  md_number_to_chars (p, 0x4c520000 | 0x0001, 4);
-
-  /* If we knew (or cared) what the source language options were, we'd
-     fill them in here.  SCO has given us permission to ignore these
-     and just set them to zero.  */
-  p = frag_more (4);
-  md_number_to_chars (p, 0x0000, 4);
-
-  frag_align (2, 0, 0);
-
-  /* We probably can't restore the current segment, for there likely
-     isn't one yet...  */
-  if (seg && subseg)
-    subseg_set (seg, subseg);
-
-}
-
-#endif /* SCO_ELF */
-
 static void
 elf_generate_asm_lineno (void)
 {
 static void
 elf_generate_asm_lineno (void)
 {
@@ -2481,6 +3127,6 @@ const struct format_ops elf_format_ops =
 #endif
   elf_obj_read_begin_hook,
   elf_obj_symbol_new_hook,
 #endif
   elf_obj_read_begin_hook,
   elf_obj_symbol_new_hook,
-  0,
+  elf_obj_symbol_clone_hook,
   elf_adjust_symtab
 };
   elf_adjust_symtab
 };