[PATCH v3 1/2] [ld] Allow symbol visibility support for ld -r

Eyal Itkin eyal.itkin@gmail.com
Fri Oct 17 17:50:10 GMT 2025


Introduce the "--localize-hidden" CLI flag, which will
be supported only for relocatables (ld -r).

When provided, hidden symbols will be converted from
STB_GLOBAL to STB_LOCAL when the relocatable is being
built. This change aligns the ET_REL creation to support
the same visibility semantics as that of the shared
library (ET_DYN).

This added support will enable users to use the
"ld -r --localize-hidden" outputs, wrapped inside
static (.a) archives, as an alternative format for the
existing static libraries.

The full discussion about the format is available in
the generic-abi group that is responsible for the ELF
standard. Specifically, the summarized architecture can
be found here:
https://groups.google.com/g/generic-abi/c/sT25-xfX9yc/m/J9SIthF4BgAJ

Signed-off-by: Eyal Itkin <eyal.itkin@gmail.com>
---
 bfd/elf64-x86-64.c                      |  3 +-
 bfd/elflink.c                           | 42 ++++++++++++++++++-------
 include/bfdlink.h                       |  3 ++
 ld/ld.h                                 |  3 ++
 ld/ldlex.h                              |  1 +
 ld/lexsup.c                             | 11 +++++++
 ld/testsuite/ld-elf/localize_hidden.map |  7 +++++
 ld/testsuite/ld-elf/localize_hidden.s   | 19 +++++++++++
 ld/testsuite/ld-elf/localize_hidden1.d  | 11 +++++++
 ld/testsuite/ld-elf/localize_hidden2.d  | 11 +++++++
 ld/testsuite/ld-elf/localize_hidden3.d  | 11 +++++++
 ld/testsuite/ld-elf/localize_hidden4.d  |  3 ++
 12 files changed, 112 insertions(+), 13 deletions(-)
 create mode 100644 ld/testsuite/ld-elf/localize_hidden.map
 create mode 100644 ld/testsuite/ld-elf/localize_hidden.s
 create mode 100644 ld/testsuite/ld-elf/localize_hidden1.d
 create mode 100644 ld/testsuite/ld-elf/localize_hidden2.d
 create mode 100644 ld/testsuite/ld-elf/localize_hidden3.d
 create mode 100644 ld/testsuite/ld-elf/localize_hidden4.d

diff --git a/bfd/elf64-x86-64.c b/bfd/elf64-x86-64.c
index 59b43149897..2c7e7c63357 100644
--- a/bfd/elf64-x86-64.c
+++ b/bfd/elf64-x86-64.c
@@ -5409,7 +5409,8 @@ elf_x86_64_finish_dynamic_symbol (bfd *output_bfd,
   if (h->got.offset != (bfd_vma) -1
       && ! GOT_TLS_GD_ANY_P (elf_x86_hash_entry (h)->tls_type)
       && elf_x86_hash_entry (h)->tls_type != GOT_TLS_IE
-      && !local_undefweak)
+      && !local_undefweak
+      && !bfd_link_relocatable (info))
     {
       Elf_Internal_Rela rela;
       asection *relgot = htab->elf.srelgot;
diff --git a/bfd/elflink.c b/bfd/elflink.c
index 3f3ea2cce51..d98e1bceb09 100644
--- a/bfd/elflink.c
+++ b/bfd/elflink.c
@@ -803,9 +803,9 @@ bfd_elf_record_link_assignment (bfd *output_bfd,
       (*bed->elf_backend_hide_symbol) (info, h, true);
     }
 
-  /* STV_HIDDEN and STV_INTERNAL symbols must be STB_LOCAL in shared objects
-     and executables.  */
-  if (!bfd_link_relocatable (info)
+  /* STV_HIDDEN and STV_INTERNAL symbols must be STB_LOCAL if hidden
+     symbols were marked for localization.  */
+  if (info->localize_hidden
       && h->dynindx != -1
       && (ELF_ST_VISIBILITY (h->other) == STV_HIDDEN
 	  || ELF_ST_VISIBILITY (h->other) == STV_INTERNAL))
@@ -3151,7 +3151,10 @@ _bfd_elf_fix_symbol_flags (struct elf_link_hash_entry *h,
 	h->def_regular = 1;
     }
 
-  /* Backend specific symbol fixup.  */
+  /* Backend specific symbol fixup, only needed for dynamic case.  */
+  if (elf_hash_table (eif->info)->dynobj == NULL)
+    return true;
+
   bed = get_elf_backend_data (elf_hash_table (eif->info)->dynobj);
   if (bed->elf_backend_fixup_symbol
       && !(*bed->elf_backend_fixup_symbol) (eif->info, h))
@@ -3197,7 +3200,7 @@ _bfd_elf_fix_symbol_flags (struct elf_link_hash_entry *h,
      visibility.  If the symbol has hidden or internal visibility, we
      will force it local.  */
   else if (h->needs_plt
-	   && bfd_link_pic (eif->info)
+	   && eif->info->localize_hidden
 	   && is_elf_hash_table (eif->info->hash)
 	   && (SYMBOLIC_BIND (eif->info, h)
 	       || ELF_ST_VISIBILITY (h->other) != STV_DEFAULT)
@@ -5680,9 +5683,9 @@ elf_link_add_object_symbols (bfd *abfd, struct bfd_link_info *info)
 		    goto error_free_vers;
 		}
 	    }
-	  else if (h->dynindx != -1)
-	    /* If the symbol already has a dynamic index, but
-	       visibility says it should not be visible, turn it into
+	  else if (info->localize_hidden
+		   && (h->dynindx != -1 || bfd_link_relocatable (info)))
+	    /* If visibility says it should not be visible, turn it into
 	       a local symbol.  */
 	    switch (ELF_ST_VISIBILITY (h->other))
 	      {
@@ -6925,6 +6928,7 @@ bfd_elf_size_dynamic_sections (bfd *output_bfd,
 {
   bfd *dynobj;
   const struct elf_backend_data *bed;
+  struct elf_info_failed asvinfo;
 
   *sinterpptr = NULL;
 
@@ -6948,10 +6952,22 @@ bfd_elf_size_dynamic_sections (bfd *output_bfd,
 
   dynobj = elf_hash_table (info)->dynobj;
 
-  if (dynobj != NULL && elf_hash_table (info)->dynamic_sections_created)
+  /* Symbol visibility should be applied in some non-dynamic cases.  */
+  if (bfd_link_relocatable (info) && info->localize_hidden)
+    {
+      /* Attach all the symbols to their version information.  */
+      asvinfo.info = info;
+      asvinfo.failed = false;
+
+      elf_link_hash_traverse (elf_hash_table (info),
+			      _bfd_elf_link_assign_sym_version,
+			      &asvinfo);
+      if (asvinfo.failed)
+	return false;
+    }
+  else if (dynobj != NULL && elf_hash_table (info)->dynamic_sections_created)
     {
       struct bfd_elf_version_tree *verdefs;
-      struct elf_info_failed asvinfo;
       struct bfd_elf_version_tree *t;
       struct bfd_elf_version_expr *d;
       asection *s;
@@ -11039,11 +11055,13 @@ elf_link_output_extsym (struct bfd_hash_entry *bh, void *data)
        && !bfd_link_relocatable (flinfo->info))
       || ((h->dynindx != -1
 	   || h->forced_local)
-	  && ((bfd_link_pic (flinfo->info)
+	  && ((flinfo->info->localize_hidden
 	       && (ELF_ST_VISIBILITY (h->other) == STV_DEFAULT
 		   || h->root.type != bfd_link_hash_undefweak))
 	      || !h->forced_local)
-	  && elf_hash_table (flinfo->info)->dynamic_sections_created))
+	  && (elf_hash_table (flinfo->info)->dynamic_sections_created
+	      || (bfd_link_relocatable (flinfo->info)
+		  && flinfo->info->localize_hidden))))
     {
       if (! ((*bed->elf_backend_finish_dynamic_symbol)
 	     (flinfo->output_bfd, flinfo->info, h, &sym)))
diff --git a/include/bfdlink.h b/include/bfdlink.h
index 3d4d71b3347..dda01662a97 100644
--- a/include/bfdlink.h
+++ b/include/bfdlink.h
@@ -486,6 +486,9 @@ struct bfd_link_info
   /* TRUE if multiple definitions should only warn.  */
   unsigned int warn_multiple_definition: 1;
 
+  /* TRUE if supports localizing hidden symbols.  */
+  unsigned int localize_hidden: 1;
+
   /* TRUE if ok to have version with no definition.  */
   unsigned int allow_undefined_version: 1;
 
diff --git a/ld/ld.h b/ld/ld.h
index c8688153bd4..83a2353cc4a 100644
--- a/ld/ld.h
+++ b/ld/ld.h
@@ -332,6 +332,9 @@ typedef struct
 
   /* Compress DWARF debug sections.  */
   enum compressed_debug_section_type compress_debug;
+
+  /* If set, localize hidden symbols.  */
+  bool localize_hidden;
 } ld_config_type;
 
 /* An enumeration of the linker phases for which resource usage information
diff --git a/ld/ldlex.h b/ld/ldlex.h
index 24cac1cdfc0..cceb455e68c 100644
--- a/ld/ldlex.h
+++ b/ld/ldlex.h
@@ -139,6 +139,7 @@ enum option_values
   OPTION_NO_ACCEPT_UNKNOWN_INPUT_ARCH,
   OPTION_PIE,
   OPTION_NO_PIE,
+  OPTION_LOCALIZE_HIDDEN,
   OPTION_UNRESOLVED_SYMBOLS,
   OPTION_WARN_UNRESOLVED_SYMBOLS,
   OPTION_ERROR_UNRESOLVED_SYMBOLS,
diff --git a/ld/lexsup.c b/ld/lexsup.c
index 5cb77992733..f4023fe4176 100644
--- a/ld/lexsup.c
+++ b/ld/lexsup.c
@@ -208,6 +208,8 @@ static const struct ld_option ld_options[] =
     'q', NULL, "Generate relocations in final output", TWO_DASHES },
   { {"relocatable", no_argument, NULL, 'r'},
     'r', NULL, N_("Generate relocatable output"), TWO_DASHES },
+  { {"localize-hidden", no_argument, NULL, OPTION_LOCALIZE_HIDDEN},
+    '\0', NULL, N_("Localize hidden relocatable symbols"), TWO_DASHES },
   { {NULL, no_argument, NULL, '\0'},
     'i', NULL, NULL, ONE_DASH },
   { {"just-symbols", required_argument, NULL, 'R'},
@@ -1237,6 +1239,7 @@ parse_args (unsigned argc, char **argv)
 		   bfd_link_dll (&link_info) ? "-shared" : "-pie");
 
 	  link_info.type = type_relocatable;
+	  link_info.localize_hidden = false;
 	  config.build_constructors = false;
 	  config.magic_demand_paged = false;
 	  config.text_read_only = false;
@@ -1347,6 +1350,7 @@ parse_args (unsigned argc, char **argv)
 		       "-shared");
 
 	      link_info.type = type_dll;
+	      link_info.localize_hidden = true;
 	      /* When creating a shared library, the default
 		 behaviour is to ignore any unresolved references.  */
 	      if (link_info.unresolved_syms_in_objects == RM_NOT_YET_SET)
@@ -1357,6 +1361,12 @@ parse_args (unsigned argc, char **argv)
 	  else
 	    fatal (_("%P: -shared not supported\n"));
 	  break;
+	case OPTION_LOCALIZE_HIDDEN:
+	  if (!bfd_link_relocatable (&link_info))
+	    fatal (_("%P: %s may only be used together with -r\n"),
+		   "--localize-hidden");
+	  link_info.localize_hidden = true;
+	  break;
 	case OPTION_NO_PIE:
 	  link_info.type = type_pde;
 	  break;
@@ -1367,6 +1377,7 @@ parse_args (unsigned argc, char **argv)
 		fatal (_("%P: -r and %s may not be used together\n"), "-pie");
 
 	      link_info.type = type_pie;
+	      link_info.localize_hidden = true;
 	    }
 	  else
 	    fatal (_("%P: -pie not supported\n"));
diff --git a/ld/testsuite/ld-elf/localize_hidden.map b/ld/testsuite/ld-elf/localize_hidden.map
new file mode 100644
index 00000000000..2429e54959d
--- /dev/null
+++ b/ld/testsuite/ld-elf/localize_hidden.map
@@ -0,0 +1,7 @@
+TEST {
+	global:
+		bar;
+
+	local:
+		*;
+};
diff --git a/ld/testsuite/ld-elf/localize_hidden.s b/ld/testsuite/ld-elf/localize_hidden.s
new file mode 100644
index 00000000000..0c5e105347c
--- /dev/null
+++ b/ld/testsuite/ld-elf/localize_hidden.s
@@ -0,0 +1,19 @@
+	.text
+	.global hidfn
+	.type	hidfn, %function
+	.hidden hidfn
+hidfn:
+	.space	4
+	.size	hidfn, 4
+
+	.global foo
+	.type	foo, %function
+foo:
+	.space	4
+	.size	foo, 4
+
+	.global bar
+	.type	bar, %function
+bar:
+	.space	4
+	.size	bar, 4
diff --git a/ld/testsuite/ld-elf/localize_hidden1.d b/ld/testsuite/ld-elf/localize_hidden1.d
new file mode 100644
index 00000000000..476eec50305
--- /dev/null
+++ b/ld/testsuite/ld-elf/localize_hidden1.d
@@ -0,0 +1,11 @@
+#source: localize_hidden.s
+#ld: -r --localize-hidden
+#readelf: -s
+
+#...
+.*: [0-9a-f]+ +4 +FUNC +LOCAL +DEFAULT +[0-9]+ hidfn
+#...
+.*: [0-9a-f]+ +4 +FUNC +GLOBAL +DEFAULT +[0-9]+ foo
+#...
+.*: [0-9a-f]+ +4 +FUNC +GLOBAL +DEFAULT +[0-9]+ bar
+#pass
diff --git a/ld/testsuite/ld-elf/localize_hidden2.d b/ld/testsuite/ld-elf/localize_hidden2.d
new file mode 100644
index 00000000000..623ee3b0a84
--- /dev/null
+++ b/ld/testsuite/ld-elf/localize_hidden2.d
@@ -0,0 +1,11 @@
+#source: localize_hidden.s
+#ld: -r
+#readelf: -s
+
+#...
+.*: [0-9a-f]+ +4 +FUNC +GLOBAL +HIDDEN +[0-9]+ hidfn
+#...
+.*: [0-9a-f]+ +4 +FUNC +GLOBAL +DEFAULT +[0-9]+ foo
+#...
+.*: [0-9a-f]+ +4 +FUNC +GLOBAL +DEFAULT +[0-9]+ bar
+#pass
diff --git a/ld/testsuite/ld-elf/localize_hidden3.d b/ld/testsuite/ld-elf/localize_hidden3.d
new file mode 100644
index 00000000000..a1f535a0663
--- /dev/null
+++ b/ld/testsuite/ld-elf/localize_hidden3.d
@@ -0,0 +1,11 @@
+#source: localize_hidden.s
+#ld: -r --localize-hidden --version-script=localize_hidden.map
+#readelf: -s
+
+#...
+.*: [0-9a-f]+ +4 +FUNC +LOCAL +DEFAULT +[0-9]+ hidfn
+#...
+.*: [0-9a-f]+ +4 +FUNC +LOCAL +DEFAULT +[0-9]+ foo
+#...
+.*: [0-9a-f]+ +4 +FUNC +GLOBAL +DEFAULT +[0-9]+ bar
+#pass
diff --git a/ld/testsuite/ld-elf/localize_hidden4.d b/ld/testsuite/ld-elf/localize_hidden4.d
new file mode 100644
index 00000000000..32689044fc3
--- /dev/null
+++ b/ld/testsuite/ld-elf/localize_hidden4.d
@@ -0,0 +1,3 @@
+#source: localize_hidden.s
+#ld: -shared --localize-hidden
+#error: --localize-hidden may only be used together with -r
-- 
2.31.1.windows.1



More information about the Binutils mailing list