[PATCH 1/3] PE-COFF: Fix weak external symbol resolution when strong undef is seen first
Alan Modra
amodra@gmail.com
Sun May 31 22:24:21 GMT 2026
On Sat, May 30, 2026 at 03:15:20PM -0400, Peter Damianov wrote:
> When linking PE-COFF objects, a weak external symbol (C_NT_WEAK with an
> aux record specifying a fallback alias) may fail to resolve if a strong
> undefined reference to the same symbol is encountered before the weak
> definition. This causes "undefined reference" errors for symbols like
> operator new or personality routines that GCC emits as weak externals
> with a fallback to a default implementation.
>
> There are two problems:
>
> 1. In coff_link_add_symbols, when the generic linker resolves a weak
> undefined against an existing strong undefined (NOACT in the action
> table), the COFF-specific symbol_class and aux record were not stored
> because the existing hash entry already had non-null class/type from
> the first (strong) object file.
>
> 2. In _bfd_coff_generic_relocate_section, the weak alias fallback only
> triggered for bfd_link_hash_undefweak symbols. When a strong undef
> is seen first, the hash type stays bfd_link_hash_undefined (the
> generic linker does not downgrade it), so the fallback was skipped.
>
> Fix by extending the condition in coff_link_add_symbols to also update
> symbol_class and aux when the incoming symbol is a PE weak external
> with aux and the existing hash is still undefined. Also extend the
> relocation handler to resolve the weak alias fallback for
> bfd_link_hash_undefined symbols that carry C_NT_WEAK class and have
> an aux record.
>
> bfd/
> * cofflink.c (coff_link_add_symbols): Also store symbol_class
> and aux record when a PE weak external with aux meets an
> existing undefined hash entry.
> (_bfd_coff_generic_relocate_section): Also resolve weak alias
> fallback for undefined symbols with C_NT_WEAK class and aux.
> ---
> bfd/cofflink.c | 48 +++++++++++++++++++++++++++++++++++++++---------
> 1 file changed, 39 insertions(+), 9 deletions(-)
>
> diff --git a/bfd/cofflink.c b/bfd/cofflink.c
> index e5c8a987d69..a38f692a008 100644
> --- a/bfd/cofflink.c
> +++ b/bfd/cofflink.c
> @@ -477,13 +477,20 @@ coff_link_add_symbols (bfd *abfd,
> /* If we don't have any symbol information currently in
> the hash table, or if we are looking at a symbol
> definition, then update the symbol class and type in
> - the hash table. */
> + the hash table. Also update if the incoming symbol is
> + a weak external with an aux record (PE COFF weak alias)
> + and the existing symbol is still undefined, so the
> + fallback alias information is preserved for the linker's
> + relocation resolution. */
> if (((*sym_hash)->symbol_class == C_NULL
> && (*sym_hash)->type == T_NULL)
> || sym.n_scnum != 0
> || (sym.n_value != 0
> && (*sym_hash)->root.type != bfd_link_hash_defined
> - && (*sym_hash)->root.type != bfd_link_hash_defweak))
> + && (*sym_hash)->root.type != bfd_link_hash_defweak)
> + || (IS_WEAK_EXTERNAL (abfd, sym)
> + && sym.n_numaux > 0
> + && (*sym_hash)->root.type == bfd_link_hash_undefined))
Should you be handling bfd_link_hash_undefweak here too?
> {
> (*sym_hash)->symbol_class = sym.n_sclass;
> if (sym.n_type != T_NULL)
> @@ -3065,14 +3072,24 @@ _bfd_coff_generic_relocate_section (bfd *output_bfd,
> + sec->output_offset);
> }
>
> - else if (h->root.type == bfd_link_hash_undefweak)
> + else if (h->root.type == bfd_link_hash_undefweak
> + || (h->root.type == bfd_link_hash_undefined
> + && h->symbol_class == C_NT_WEAK && h->numaux == 1))
> {
> - if (h->symbol_class == C_NT_WEAK && h->numaux == 1)
> + /* Weak undefined symbol: either GNU weak (no aux record) or
> + PE COFF weak external (C_NT_WEAK with aux record).
> + Also handles strong undefined symbols that carry PE weak
> + external metadata (when strong undef is seen before weak def,
> + the hash type stays bfd_link_hash_undefined but we preserve
> + the weak external class and aux for later resolution). */
> +
> + bool is_pe_weak = (h->symbol_class == C_NT_WEAK && h->numaux == 1);
> +
> + if (is_pe_weak)
> {
> - /* See _Microsoft Portable Executable and Common Object
> + /* PE COFF weak external: resolve via fallback alias.
> + See _Microsoft Portable Executable and Common Object
> File Format Specification_, section 5.5.3.
> - Note that weak symbols without aux records are a GNU
> - extension.
> FIXME: All weak externals are treated as having
> characteristic IMAGE_WEAK_EXTERN_SEARCH_NOLIBRARY (1).
> These behave as per SVR4 ABI: A library member
> @@ -3081,24 +3098,37 @@ _bfd_coff_generic_relocate_section (bfd *output_bfd,
> See also linker.c: generic_link_check_archive_element. */
> struct coff_link_hash_entry *h2 = NULL;
> unsigned long symndx2 = h->aux->x_sym.x_tagndx.u32;
> +
> if (symndx2 < obj_raw_syment_count (h->auxbfd))
> h2 = obj_coff_sym_hashes (h->auxbfd)[symndx2];
>
> if (!h2 || h2->root.type == bfd_link_hash_undefined)
> {
> + /* Fallback alias not found or still undefined.
> + Resolve to NULL. */
> sec = bfd_abs_section_ptr;
> val = 0;
> }
> else
> {
> + /* Use fallback alias target. */
> sec = h2->root.u.def.section;
> val = h2->root.u.def.value
> + sec->output_section->vma + sec->output_offset;
> }
> }
> else
> - /* This is a GNU extension. */
> - val = 0;
> + {
> + /* GNU extension: ELF-style weak symbol in COFF without
> + PE weak external aux record. COFF has no native support
> + for weak symbols (unlike ELF where they're part of the
> + format). PE COFF adds them via C_NT_WEAK storage class
> + with an aux record pointing to a fallback symbol. GNU ld
> + extends this by allowing __attribute__((weak)) in COFF
> + objects even without the PE aux structure, treating them
> + like ELF weak symbols: resolve to NULL if not defined. */
> + val = 0;
> + }
> }
>
> else if (! bfd_link_relocatable (info))
> --
> 2.54.0
--
Alan Modra
More information about the Binutils
mailing list