[v3,3/4] bfd: Handle PE weak externals with real fallbacks in archives

Message ID 8292b88c6b7216d8eedba27e1d1bd6a8c7da34d5.1782245474.git.peter0x44@disroot.org
State New
Headers
Series PE-COFF weak external fixes |

Commit Message

Peter0x44 June 23, 2026, 9:38 p.m. UTC
  PE weak externals are canonicalized as undefined BFD symbols, even
when their auxiliary entry names a real fallback definition in the same
object.  Archive map generation therefore skipped the public weak name,
so a strong undefined reference to that name could not extract the
archive member.

Include such PE weak externals in the archive map, but only when the
fallback is a real definition rather than the absolute-zero null symbol
used for weak declarations with no fallback.  Also avoid extracting an
archive member again during repeated archive searches once the weak
external resolves through a defined fallback.

Fixes: https://gcc.gnu.org/PR124263

bfd/

	* archive.c: Include coff-bfd.h.
	(_bfd_compute_and_push_armap): Include PE weak externals in
	the archive map when their fallback is a real definition.
	* coff-bfd.c (bfd_coff_pe_weak_external_has_real_fallback): New
	function.
	* coff-bfd.h (bfd_coff_pe_weak_external_has_real_fallback):
	Declare.
	* cofflink.c (coff_link_hash_pe_weak_external_has_real_fallback):
	New function.
	(coff_link_check_archive_element): Avoid extracting an archive
	member again for a PE weak external whose real fallback is
	already defined.
---
 bfd/archive.c  | 22 ++++++++++++++++------
 bfd/coff-bfd.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++
 bfd/coff-bfd.h |  3 +++
 bfd/cofflink.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 112 insertions(+), 6 deletions(-)
  

Patch

diff --git a/bfd/archive.c b/bfd/archive.c
index 44030eb3d63..1bff31b6d09 100644
--- a/bfd/archive.c
+++ b/bfd/archive.c
@@ -142,6 +142,7 @@  SUBSECTION
 #include "filenames.h"
 #include "bfdlink.h"
 #include "plugin.h"
+#include "coff-bfd.h"
 
 #ifndef errno
 extern int errno;
@@ -2535,12 +2536,21 @@  _bfd_compute_and_push_armap
 		  flagword flags = (syms[src_count])->flags;
 		  asection *sec = syms[src_count]->section;
 
-		  if (((flags & (BSF_GLOBAL
-				 | BSF_WEAK
-				 | BSF_INDIRECT
-				 | BSF_GNU_UNIQUE)) != 0
-		       || bfd_is_com_section (sec))
-		      && ! bfd_is_und_section (sec))
+		  /* Include symbols that normally define archive-map entries, plus
+		     PE weak externals whose fallback is a real definition.  Those
+		     weak externals are canonicalized as undefined symbols, but their
+		     public names can still be needed to pull the archive member.  */
+		  bool include_in_armap
+		    = ((((flags & (BSF_GLOBAL
+			      | BSF_WEAK
+			      | BSF_INDIRECT
+			      | BSF_GNU_UNIQUE)) != 0
+			 || bfd_is_com_section (sec))
+			&& ! bfd_is_und_section (sec))
+		       || bfd_coff_pe_weak_external_has_real_fallback
+			    (bfd_asymbol_bfd (syms[src_count]), syms[src_count]));
+
+		  if (include_in_armap)
 		    {
 		      bfd_size_type namelen;
 		      struct orl *new_map;
diff --git a/bfd/coff-bfd.c b/bfd/coff-bfd.c
index d836355aa64..e57135ddd18 100644
--- a/bfd/coff-bfd.c
+++ b/bfd/coff-bfd.c
@@ -110,3 +110,50 @@  bfd_coff_get_auxent (bfd *abfd,
 
   return true;
 }
+
+/* Return TRUE if SYMBOL is a PE weak external whose fallback symbol is
+   a real definition.  A weak declaration with no fallback uses the COFF
+   null symbol as its fallback; do not treat that as an archive provider.  */
+
+bool
+bfd_coff_pe_weak_external_has_real_fallback (bfd *abfd,
+					    asymbol *symbol)
+{
+  coff_symbol_type *csym;
+  combined_entry_type *aux;
+  combined_entry_type *fallback;
+
+  if (bfd_get_flavour (abfd) != bfd_target_coff_flavour
+      || coff_data (abfd) == NULL
+      || ! obj_pe (abfd))
+    return false;
+
+  csym = coff_symbol_from (symbol);
+  if (csym == NULL
+      || csym->native == NULL
+      || ! csym->native->is_sym
+      || csym->native->u.syment.n_sclass != C_NT_WEAK
+      || csym->native->u.syment.n_numaux != 1)
+    return false;
+
+  aux = csym->native + 1;
+  if (aux->is_sym)
+    return false;
+
+  if (aux->fix_tag)
+    fallback = (combined_entry_type *) aux->u.auxent.x_sym.x_tagndx.p;
+  else
+    {
+      uint32_t tagndx = aux->u.auxent.x_sym.x_tagndx.u32;
+
+      if (tagndx >= obj_raw_syment_count (abfd))
+	return false;
+      fallback = obj_raw_syments (abfd) + tagndx;
+    }
+
+  return (fallback != NULL
+	  && fallback->is_sym
+	  && fallback->u.syment.n_scnum != N_UNDEF
+	  && !(fallback->u.syment.n_scnum == N_ABS
+	       && fallback->u.syment.n_value == 0));
+}
diff --git a/bfd/coff-bfd.h b/bfd/coff-bfd.h
index 70cc0c36902..6626c491ebb 100644
--- a/bfd/coff-bfd.h
+++ b/bfd/coff-bfd.h
@@ -81,5 +81,8 @@  extern bool bfd_coff_get_syment
 extern bool bfd_coff_get_auxent
   (bfd *, struct bfd_symbol *, int, union internal_auxent *);
 
+extern bool bfd_coff_pe_weak_external_has_real_fallback
+  (bfd *, struct bfd_symbol *);
+
 extern bool bfd_coff_set_symbol_class
   (bfd *, struct bfd_symbol *, unsigned int);
diff --git a/bfd/cofflink.c b/bfd/cofflink.c
index 75b4c47eacf..d029bec1678 100644
--- a/bfd/cofflink.c
+++ b/bfd/cofflink.c
@@ -36,6 +36,45 @@  static bool coff_link_check_archive_element
    bool *);
 static bool coff_link_add_symbols (bfd *, struct bfd_link_info *);
 
+static bool
+coff_link_hash_pe_weak_external_has_real_fallback
+  (struct coff_link_hash_entry *h)
+{
+  struct coff_link_hash_entry *h2;
+  unsigned long symndx;
+
+  if (h->symbol_class != C_NT_WEAK
+      || h->numaux != 1
+      || h->aux == NULL
+      || h->auxbfd == NULL
+      || ! obj_pe (h->auxbfd)
+      || obj_coff_sym_hashes (h->auxbfd) == NULL)
+    return false;
+
+  /* The PE weak-external aux entry names the fallback symbol by raw
+     symbol index.  Look up the corresponding link hash entry so we can
+     test the fallback's resolved state, not just its object-file entry.  */
+  symndx = h->aux->x_sym.x_tagndx.u32;
+  if (symndx >= obj_raw_syment_count (h->auxbfd))
+    return false;
+
+  h2 = obj_coff_sym_hashes (h->auxbfd)[symndx];
+  if (h2 == NULL)
+    return false;
+
+  while (h2->root.type == bfd_link_hash_indirect
+	 || h2->root.type == bfd_link_hash_warning)
+    h2 = (struct coff_link_hash_entry *) h2->root.u.i.link;
+
+  /* A weak declaration with no fallback uses the absolute-zero null
+     symbol.  Only a defined real fallback means this archive member has
+     already satisfied the weak external.  */
+  return ((h2->root.type == bfd_link_hash_defined
+	   || h2->root.type == bfd_link_hash_defweak)
+	  && !(bfd_is_abs_section (h2->root.u.def.section)
+	       && h2->root.u.def.value == 0));
+}
+
 /* Return TRUE if SYM is a weak, external symbol.  */
 #define IS_WEAK_EXTERNAL(abfd, sym)			\
   ((sym).n_sclass == C_WEAKEXT				\
@@ -255,6 +294,13 @@  coff_link_check_archive_element (bfd *abfd,
   if (((struct coff_link_hash_entry *) h)->indx == -3)
     return true;
 
+  /* A PE weak external can stay undefined even after its fallback has
+     been defined by this archive member.  Avoid extracting the member
+     again if the same archive is searched more than once.  */
+  if (coff_link_hash_pe_weak_external_has_real_fallback
+      ((struct coff_link_hash_entry *) h))
+    return true;
+
   /* Include this element?  */
   if (!(*info->callbacks->add_archive_element) (info, abfd, name, &abfd))
     return true;