gas: Extend .symver directive
[deliverable/binutils-gdb.git] / gas / config / obj-elf.c
index 5e7e8f0877963107b367965eda2ab5288b20900a..409ea4d6bed5ef4542bc772e9b7b372f9026bc1c 100644 (file)
@@ -1515,6 +1515,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
@@ -1525,9 +1589,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 ();
 
@@ -1546,43 +1613,59 @@ obj_elf_symver (int ignore ATTRIBUTE_UNUSED)
   lex_type[(unsigned char) '@'] |= LEX_NAME;
   c = get_symbol_name (& name);
   lex_type[(unsigned char) '@'] = old_lexat;
   lex_type[(unsigned char) '@'] |= LEX_NAME;
   c = get_symbol_name (& name);
   lex_type[(unsigned char) '@'] = old_lexat;
+  sym_name = S_GET_NAME (sym);
 
   if (S_IS_COMMON (sym))
     {
       as_bad (_("`%s' can't be versioned to common symbol '%s'"),
 
   if (S_IS_COMMON (sym))
     {
       as_bad (_("`%s' can't be versioned to common symbol '%s'"),
-             name, S_GET_NAME (sym));
+             name, sym_name);
       ignore_rest_of_line ();
       return;
     }
 
       ignore_rest_of_line ();
       return;
     }
 
-  if (symbol_get_obj (sym)->versioned_name == NULL)
+  p = strchr (name, ELF_VER_CHR);
+  if (p == NULL)
     {
     {
-      symbol_get_obj (sym)->versioned_name = xstrdup (name);
+      as_bad (_("missing version name in `%s' for symbol `%s'"),
+             name, sym_name);
+      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;
+    }
 
 
-      (void) restore_line_pointer (c);
+  (void) restore_line_pointer (c);
 
 
-      if (strchr (symbol_get_obj (sym)->versioned_name,
-                 ELF_VER_CHR) == NULL)
+  if (*input_line_pointer == ',')
+    {
+      char *save = input_line_pointer;
+
+      ++input_line_pointer;
+      SKIP_WHITESPACE ();
+      if (strncmp (input_line_pointer, "local", 5) == 0)
        {
        {
-         as_bad (_("missing version name in `%s' for symbol `%s'"),
-                 symbol_get_obj (sym)->versioned_name,
-                 S_GET_NAME (sym));
-         ignore_rest_of_line ();
-         return;
+         input_line_pointer += 5;
+         sy_obj->visibility = visibility_local;
        }
        }
-    }
-  else
-    {
-      if (strcmp (symbol_get_obj (sym)->versioned_name, name))
+      else if (strncmp (input_line_pointer, "hidden", 6) == 0)
        {
        {
-         as_bad (_("multiple versions [`%s'|`%s'] for symbol `%s'"),
-                 name, symbol_get_obj (sym)->versioned_name,
-                 S_GET_NAME (sym));
-         ignore_rest_of_line ();
-         return;
+         input_line_pointer += 6;
+         sy_obj->visibility = visibility_hidden;
        }
        }
-
-      (void) restore_line_pointer (c);
+      else if (strncmp (input_line_pointer, "remove", 6) == 0)
+       {
+         input_line_pointer += 6;
+         sy_obj->visibility = visibility_remove;
+       }
+      else
+       input_line_pointer = save;
     }
 
   demand_empty_rest_of_line ();
     }
 
   demand_empty_rest_of_line ();
@@ -2382,6 +2465,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)
@@ -2409,73 +2493,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);
-      if (p == NULL)
-       /* We will have already reported an error about a missing version.  */
-       *puntp = TRUE;
-
       /* This symbol was given a new name with the .symver directive.
       /* 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.  */
+        against the correct versioned symbol.  */
 
 
-      else if (! S_IS_DEFINED (symp))
+      /* 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)
-           {
-             size_t l;
-
-             /* The @@@ syntax is a special case. It renames the
-                symbol name to versioned_name with one `@' removed.  */
-             l = strlen (&p[3]) + 1;
-             memmove (&p[2], &p[3], l);
-             S_SET_NAME (symp, sy_obj->versioned_name);
-           }
-         else
+         asymbol *bfdsym;
+         elf_symbol_type *elfsym;
+
+         /* This is a definition.  Add an alias for each version.
+            FIXME: Using an alias will permit the debugging information
+            to refer to the right symbol.  However, it's not clear
+            whether it is the best approach.  */
+
+         /* FIXME: Creating a new symbol here is risky.  We're
+            in the final loop over the symbol table.  We can
+            get away with it only because the symbol goes to
+            the end of the list, where the loop will still see
+            it.  It would probably be better to do this in
+            obj_frob_file_before_adjust.  */
+         for (; versioned_name != NULL;
+              versioned_name = versioned_name->next)
            {
            {
-             symbolS *symp2;
-
-             /* FIXME: Creating a new symbol here is risky.  We're
-                in the final loop over the symbol table.  We can
-                get away with it only because the symbol goes to
-                the end of the list, where the loop will still see
-                it.  It would probably be better to do this in
-                obj_frob_file_before_adjust.  */
-
-             symp2 = symbol_find_or_make (sy_obj->versioned_name);
-
-             /* Now we act as though we saw symp2 = sym.  */
-             if (S_IS_COMMON (symp))
-               {
-                 as_bad (_("`%s' can't be versioned to common symbol '%s'"),
-                         sy_obj->versioned_name, S_GET_NAME (symp));
-                 *puntp = TRUE;
-                 return;
-               }
+             symbolS *symp2 = symbol_find_or_make (versioned_name->name);
 
              S_SET_SEGMENT (symp2, S_GET_SEGMENT (symp));
 
 
              S_SET_SEGMENT (symp2, S_GET_SEGMENT (symp));
 
@@ -2498,6 +2556,27 @@ 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 (bfd_asymbol_bfd (bfdsym),
+                                       bfdsym);
+             elfsym->internal_elf_sym.st_other &= ~3;
+             elfsym->internal_elf_sym.st_other |= STV_HIDDEN;
+             break;
+           case visibility_remove:
+             /* Remove the symbol if it isn't used in relocation.  */
+             if (!symbol_used_in_reloc_p (symp))
+               symbol_remove (symp, &symbol_rootP, &symbol_lastP);
+             break;
+           case visibility_local:
+             S_CLEAR_EXTERNAL (symp);
+             break;
+           }
        }
     }
 
        }
     }
 
@@ -2676,36 +2755,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);
-               if (p != NULL && p[1] == ELF_VER_CHR && p[2] == ELF_VER_CHR)
-                 {
-                   size_t l = strlen (&p[3]) + 1;
-                   memmove (&p[1], &p[3], l);
-                 }
-               if (symbol_used_p (symp) == 0
-                   && symbol_used_in_reloc_p (symp) == 0)
-                 symbol_remove (symp, &symbol_rootP, &symbol_lastP);
-             }
+       {
+         struct elf_obj_sy *sy_obj = symbol_get_obj (symp);
+         int is_defined = !!S_IS_DEFINED (symp);
 
 
-           /* If there was .weak foo, but foo was neither defined nor
-              used anywhere, remove it.  */
+         if (sy_obj->versioned_name)
+           {
+             char *p = strchr (sy_obj->versioned_name->name,
+                               ELF_VER_CHR);
 
 
-           else if (S_IS_WEAK (symp)
-                    && symbol_used_p (symp) == 0
-                    && symbol_used_in_reloc_p (symp) == 0)
-             symbol_remove (symp, &symbol_rootP, &symbol_lastP);
-         }
+             if (sy_obj->rename)
+               {
+                 /* The @@@ syntax is a special case. If the symbol is
+                    not defined, 2 `@'s will be removed from the
+                    versioned_name. Otherwise, 1 `@' will be removed.   */
+                 size_t l = strlen (&p[3]) + 1;
+                 memmove (&p[1 + is_defined], &p[3], l);
+               }
+
+             if (!is_defined)
+               {
+                 /* Verify that the name isn't using the @@ syntax--this
+                    is reserved for definitions of the default version
+                    to link against.  */
+                 if (!sy_obj->rename && p[1] == ELF_VER_CHR)
+                   {
+                     as_bad (_("invalid attempt to declare external "
+                               "version name as default in symbol `%s'"),
+                             sy_obj->versioned_name->name);
+                     return;
+                   }
+
+                 /* Only one version symbol is allowed for undefined
+                    symbol.  */
+                 if (sy_obj->versioned_name->next)
+                   {
+                     as_bad (_("multiple versions [`%s'|`%s'] for "
+                               "symbol `%s'"),
+                             sy_obj->versioned_name->name,
+                             sy_obj->versioned_name->next->name,
+                             S_GET_NAME (symp));
+                     return;
+                   }
+
+                 sy_obj->rename = TRUE;
+               }
+           }
+
+         /* If there was .symver or .weak, but symbol was neither
+            defined nor used anywhere, remove it.  */
+         if (!is_defined
+             && (sy_obj->versioned_name || S_IS_WEAK (symp))
+             && symbol_used_p (symp) == 0
+             && symbol_used_in_reloc_p (symp) == 0)
+           symbol_remove (symp, &symbol_rootP, &symbol_lastP);
+       }
     }
 }
 
     }
 }
 
This page took 0.045969 seconds and 4 git commands to generate.