From patchwork Sat May 30 19:15:20 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Peter0x44 X-Patchwork-Id: 136124 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 0595C4BA900B for ; Sat, 30 May 2026 19:16:42 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 0595C4BA900B DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1780168602; bh=7BfDRQHZtdT95rI4fqfnadYZdXp8jCCx/foHV/F9La4=; h=To:Cc:Subject:Date:In-Reply-To:References:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From:Reply-To:From; b=cExZTKtEYPVVxz44O9TUPwbIm/cmcKHaa9BlzOTN7KTZ/NoAM5Ps9DSWFsGLqQEwr sMDLPkniMR1O6TyNrxxiGXqDNqJOTz6cCkYzjGLj+TnqaSaBDelFLUfLcsEKMmFqqk 06zISPd1WDtC5XtUyDbAuhnoJ7IVgtpwYill/CMw= X-Original-To: binutils@sourceware.org Delivered-To: binutils@sourceware.org Received: from layka.disroot.org (layka.disroot.org [178.21.23.139]) by sourceware.org (Postfix) with ESMTPS id 3B0AB4BA7996 for ; Sat, 30 May 2026 19:16:06 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 3B0AB4BA7996 ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 3B0AB4BA7996 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1780168566; cv=none; b=sso6ZaVCOJv7856mgR3QTNbnG571ZDfnyFKqTvkZB9UkWLg6+W2uhZ7oPkbOhT+MTh9t4N6iOcAoYW/bWhrUF1Y06IQj3ThiHC4BFj9p7WfPR7oXtWJ2eriJ6K1VDcbkGIl8bJxtnEGJPKliKITjSmOoF6tY1N0Mzye8VyCM9E8= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1780168566; c=relaxed/simple; bh=mNrVpKmL8QD7CIzE7wE0Cj9ZqwQYhM24qm0jEMG12UI=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=CHP0/xbAeb8yp1nOlR2/4ktjKHIhTNDDLI5Sjf/y62K2KpWvHxhcNWBtaGfMR79T6vY1oVnmVnke/PXHHz06uwOgg5XgjRU+M2a7l/dUZhlHc6EvCyNkse6dSTOFuq0a/7D967DNoKi8XVQR+pzKQ/ZM3RadmG1OTYNRT/Br1w4= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (2048-bit key, secure) header.d=disroot.org header.i=@disroot.org header.a=rsa-sha256 header.s=mail header.b=V8NLoiKI DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 3B0AB4BA7996 Received: from mail01.disroot.lan (localhost [127.0.0.1]) by disroot.org (Postfix) with ESMTP id C62402677F; Sat, 30 May 2026 21:16:04 +0200 (CEST) X-Virus-Scanned: SPAM Filter at disroot.org Received: from layka.disroot.org ([127.0.0.1]) by localhost (disroot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id Hqnj-xezBQTz; Sat, 30 May 2026 21:16:04 +0200 (CEST) To: binutils@sourceware.org Cc: martin@martin.st, pali@kernel.org, lh_mouse@126.com, oleg.tolmatcev@gmail.com, amodra@gmail.com, jbeulich@suse.com, Peter Damianov Subject: [PATCH 1/3] PE-COFF: Fix weak external symbol resolution when strong undef is seen first Date: Sat, 30 May 2026 15:15:20 -0400 Message-ID: <20260530191522.57144-2-peter0x44@disroot.org> In-Reply-To: <20260530191522.57144-1-peter0x44@disroot.org> References: <20260530191522.57144-1-peter0x44@disroot.org> MIME-Version: 1.0 X-Spam-Status: No, score=-11.9 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_BLOCKED, SPF_HELO_NONE, SPF_PASS, TXREP, URIBL_BLOCKED shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: binutils@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Binutils mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Peter Damianov via Binutils From: Peter0x44 Reply-To: Peter Damianov Errors-To: binutils-bounces~patchwork=sourceware.org@sourceware.org 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)) { (*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)) From patchwork Sat May 30 19:15:21 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Peter0x44 X-Patchwork-Id: 136125 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 12AE24BA79A4 for ; Sat, 30 May 2026 19:16:43 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 12AE24BA79A4 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1780168603; bh=t5yf1MXTQVqYw0H3OsRRIBjPGE3+YAcJ4dRLcuVw6Zc=; h=To:Cc:Subject:Date:In-Reply-To:References:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From:Reply-To:From; b=MNcUUdv+KCSx1Ylq37Xseldfcyzpta7q7in9tuiAJWTg+B2qDrHw2SS7sh7GmDeBx gTMMYoU2DeXzXnQ++4JLBVfh++Q9ZyB59+BdI/Cm9I1Iz+70kh5NpqtVd/TFkiVHwd cJFKdpF2XOqB/TQChiJRBCVGMtwiPR/FZzTc6wAo= X-Original-To: binutils@sourceware.org Delivered-To: binutils@sourceware.org Received: from layka.disroot.org (layka.disroot.org [178.21.23.139]) by sourceware.org (Postfix) with ESMTPS id 121874BA7991 for ; Sat, 30 May 2026 19:16:08 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 121874BA7991 ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 121874BA7991 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1780168568; cv=none; b=PnkZlxVsM8EXOykN48u5S/RtzoQkRNVRTI/TQ95umiYHAbZfYGX4Swrw2VFeNXjPa1qejo0BbuMQhyNhQZsvtOcbVW7s4yyMowT851WCyXY/1S5riTJTGZhzG99YUkk9aW7A7SVJnNLd20b9vz2ppPJkD8ivtTIvCO3pL0qMpgo= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1780168568; c=relaxed/simple; bh=MEFJuBPqHGpfLJey5VgbOGIeDTZyCWG5JGqOOTKrE0g=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=wPe77xisBpur9diKQQSW30p8LSu5WK4hWzhIah1Lq6s7bZnKd+lDGCQEeBsBBklZEZWtTRfaoR3FDsg6E2E4egosQZq6dNnuOUC/kQs8zUxc0xaqYL7BL6ZE40qgCM2Lw6KLezmuKRYsolu3whPZKQyBamjU0tsAEX40I9mxZ2k= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (2048-bit key, secure) header.d=disroot.org header.i=@disroot.org header.a=rsa-sha256 header.s=mail header.b=feHCTB7q DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 121874BA7991 Received: from mail01.disroot.lan (localhost [127.0.0.1]) by disroot.org (Postfix) with ESMTP id B9FB9267F0; Sat, 30 May 2026 21:16:06 +0200 (CEST) X-Virus-Scanned: SPAM Filter at disroot.org Received: from layka.disroot.org ([127.0.0.1]) by localhost (disroot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 6YoUjR4iHskH; Sat, 30 May 2026 21:16:06 +0200 (CEST) To: binutils@sourceware.org Cc: martin@martin.st, pali@kernel.org, lh_mouse@126.com, oleg.tolmatcev@gmail.com, amodra@gmail.com, jbeulich@suse.com, Peter Damianov Subject: [PATCH 2/3] PE-COFF: Prefer weak external with defined fallback over null fallback Date: Sat, 30 May 2026 15:15:21 -0400 Message-ID: <20260530191522.57144-3-peter0x44@disroot.org> In-Reply-To: <20260530191522.57144-1-peter0x44@disroot.org> References: <20260530191522.57144-1-peter0x44@disroot.org> MIME-Version: 1.0 X-Spam-Status: No, score=-11.2 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_STOCKGEN, RCVD_IN_DNSWL_BLOCKED, SPF_HELO_NONE, SPF_PASS, TXREP, URIBL_BLOCKED shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: binutils@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Binutils mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Peter Damianov via Binutils From: Peter0x44 Reply-To: Peter Damianov Errors-To: binutils-bounces~patchwork=sourceware.org@sourceware.org When two PE COFF weak externals for the same symbol are linked (both C_NT_WEAK with aux records), the generic linker takes no action on the second one (NOACT: weak undef meets existing weak undef). This means the first file's fallback alias always wins regardless of whether it points to a real definition or to NULL. This causes a problem when a weak declaration (fallback = NULL, i.e. "resolve to NULL if nothing provides it") is linked before a weak definition (fallback = actual function body). The symbol resolves to address 0 at runtime, causing a crash when called. Fix this by comparing the fallback targets when two weak externals meet: if the incoming weak external's fallback resolves to a defined symbol and the existing one does not (or points to NULL), update the aux record to use the better fallback. This matches the behavior of lld after: https://github.com/llvm/llvm-project/commit/7ca5698b4c3698d06065e0941df7f23d72913d23 bfd/ * cofflink.c (coff_link_add_symbols): When two PE COFF weak externals meet, prefer the one whose fallback alias resolves to a defined symbol. --- bfd/cofflink.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/bfd/cofflink.c b/bfd/cofflink.c index a38f692a008..93ecfebef03 100644 --- a/bfd/cofflink.c +++ b/bfd/cofflink.c @@ -543,6 +543,76 @@ coff_link_add_symbols (bfd *abfd, (*sym_hash)->aux = alloc; } } + + /* When two PE COFF weak externals meet (both with aux + records specifying fallback aliases), prefer the one + whose fallback resolves to a defined symbol over one + whose fallback is undefined or NULL. This + handles the case where a weak declaration (with a + fallback of NULL) is seen before a weak + definition (with a fallback of the actual function + body). */ + else if (IS_WEAK_EXTERNAL (abfd, sym) + && sym.n_numaux > 0 + && (*sym_hash)->root.type == bfd_link_hash_undefweak + && (*sym_hash)->symbol_class == C_NT_WEAK + && (*sym_hash)->numaux == 1) + { + /* Parse the incoming aux to get the fallback tagndx. */ + union internal_auxent new_aux; + bfd_coff_swap_aux_in (abfd, esym + symesz, sym.n_type, + sym.n_sclass, 0, sym.n_numaux, + &new_aux); + unsigned long new_tagndx = new_aux.x_sym.x_tagndx.u32; + struct coff_link_hash_entry *h2_new = NULL; + struct coff_link_hash_entry *h2_old = NULL; + + if (new_tagndx < obj_raw_syment_count (abfd)) + h2_new = obj_coff_sym_hashes (abfd)[new_tagndx]; + + unsigned long old_tagndx + = (*sym_hash)->aux->x_sym.x_tagndx.u32; + if (old_tagndx + < obj_raw_syment_count ((*sym_hash)->auxbfd)) + h2_old = obj_coff_sym_hashes + ((*sym_hash)->auxbfd)[old_tagndx]; + + /* Update if the new fallback is defined but the old + one is not (or points to NULL). */ + if (h2_new != NULL + && (h2_new->root.type == bfd_link_hash_defined + || h2_new->root.type == bfd_link_hash_defweak) + && (h2_old == NULL + || h2_old->root.type == bfd_link_hash_undefined + || h2_old->root.type == bfd_link_hash_undefweak + || (h2_old->root.type == bfd_link_hash_defined + && bfd_is_abs_section + (h2_old->root.u.def.section) + && h2_old->root.u.def.value == 0))) + { + union internal_auxent *alloc; + unsigned int i; + bfd_byte *eaux; + union internal_auxent *iaux; + + (*sym_hash)->symbol_class = sym.n_sclass; + (*sym_hash)->auxbfd = abfd; + (*sym_hash)->numaux = sym.n_numaux; + alloc = ((union internal_auxent *) + bfd_hash_allocate (&info->hash->table, + (sym.n_numaux + * sizeof (*alloc)))); + if (alloc == NULL) + goto error_return; + for (i = 0, eaux = esym + symesz, iaux = alloc; + i < sym.n_numaux; + i++, eaux += symesz, iaux++) + bfd_coff_swap_aux_in (abfd, eaux, sym.n_type, + sym.n_sclass, (int) i, + sym.n_numaux, iaux); + (*sym_hash)->aux = alloc; + } + } } if (classification == COFF_SYMBOL_PE_SECTION From patchwork Sat May 30 19:15:22 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Peter0x44 X-Patchwork-Id: 136126 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 584FA4BA799D for ; Sat, 30 May 2026 19:17:59 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 584FA4BA799D DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1780168679; bh=14DL6AAjPX6HtN+lXYNgxuywdCEWNnCkoZt+INjryYU=; h=To:Cc:Subject:Date:In-Reply-To:References:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From:Reply-To:From; b=S84H8B3uHXg5tLI99TGL6SWTgNwFok//NsV0x8EO38Erdlmvwsk8wTdSV6ZQ4c/Mn Kqa9siOhtZ7mzI2Xmu2M0ARVDikLgf2QTKxC20xa0BUuxAHtLJXyH8zifvwDd821Dg B3Bwn2YUKarhCkdYyDRm73BVJULxBJ2GCUNz3Sus= X-Original-To: binutils@sourceware.org Delivered-To: binutils@sourceware.org Received: from layka.disroot.org (layka.disroot.org [178.21.23.139]) by sourceware.org (Postfix) with ESMTPS id 78CF24BA79A3 for ; Sat, 30 May 2026 19:16:09 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 78CF24BA79A3 ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 78CF24BA79A3 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1780168569; cv=none; b=NiISTTtPZHO9YyHrgDaLVw6guMQnTJZfr0u/YUHzMr+8hIsduOtPERJDYwb0zdI3hEJy0JCYbfHUvLVoaNayJc7NuXWW9SikSm38f86CFPr4EhWlmbac3DeVH3FUZUYyIYJTz9l12PE8ZbalxrNj2xM2Aosmw8d6YszsAI04gQU= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1780168569; c=relaxed/simple; bh=5a9rBhyxdZB5jgrNcjy1MdWEEngMnECyHQPdkE/BhWo=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=DDejL7x6PrHX2IVNCX6pyyCaUVaRYUxF3rBxX7Fd5xcvoLYFuNfoHhAeiMHWpiKwcAvuHTSVBulc4MdFPK0Cgp+ZW/08B+Gr0scHQjqX7fuWOmBZ43TEHYldXH29JQoLGYqET2OYbSlyjSQjzA3F3xotmiukkALlMW8chVMl5Bg= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (2048-bit key, secure) header.d=disroot.org header.i=@disroot.org header.a=rsa-sha256 header.s=mail header.b=EdUHSQpC DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 78CF24BA79A3 Received: from mail01.disroot.lan (localhost [127.0.0.1]) by disroot.org (Postfix) with ESMTP id 5EF6E26AF7; Sat, 30 May 2026 21:16:08 +0200 (CEST) X-Virus-Scanned: SPAM Filter at disroot.org Received: from layka.disroot.org ([127.0.0.1]) by localhost (disroot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id GhBmo4BLza2x; Sat, 30 May 2026 21:16:07 +0200 (CEST) To: binutils@sourceware.org Cc: martin@martin.st, pali@kernel.org, lh_mouse@126.com, oleg.tolmatcev@gmail.com, amodra@gmail.com, jbeulich@suse.com, Peter Damianov Subject: [PATCH 3/3] ld/testsuite: Add comprehensive PE COFF weak external tests Date: Sat, 30 May 2026 15:15:22 -0400 Message-ID: <20260530191522.57144-4-peter0x44@disroot.org> In-Reply-To: <20260530191522.57144-1-peter0x44@disroot.org> References: <20260530191522.57144-1-peter0x44@disroot.org> MIME-Version: 1.0 X-Spam-Status: No, score=-11.9 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, PROLO_LEO1, SPF_HELO_NONE, SPF_PASS, TXREP, URIBL_BLOCKED shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: binutils@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Binutils mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Peter Damianov via Binutils From: Peter0x44 Reply-To: Peter Damianov Errors-To: binutils-bounces~patchwork=sourceware.org@sourceware.org Add tests covering the full matrix of weak/strong symbol interactions for PE COFF weak externals, based on testcases by Martin Storsjo. ld/testsuite/ * ld-pe/pe-compile.exp (weak_ext_test): New proc. Compiles source files, links via gcc, and runs natively if possible. Add tests: normal, weak-undef, weak-defined, weak-decl-weak-def, weak-use, weak-override, weak-duplicate, weak-def-override, weak-def-use. * ld-pe/weak-ext-main.c: New file. * ld-pe/weak-ext-main-weak.c: New file. * ld-pe/weak-ext-main-weak-def.c: New file. * ld-pe/weak-ext-add2.c: New file. * ld-pe/weak-ext-add1-weak-chained.c: New file. * ld-pe/weak-ext-dummy.c: New file. * ld-pe/weak-ext-expected1.c: New file. * ld-pe/weak-ext-expected3.c: New file. * ld-pe/weak-ext-expected5.c: New file. * ld-pe/weak-ext-expected3-add1-weak.c: New file. --- ld/testsuite/ld-pe/pe-compile.exp | 88 +++++++++++++++++++ .../ld-pe/weak-ext-add1-weak-chained.c | 13 +++ ld/testsuite/ld-pe/weak-ext-add2.c | 8 ++ ld/testsuite/ld-pe/weak-ext-dummy.c | 5 ++ ld/testsuite/ld-pe/weak-ext-expected1.c | 1 + .../ld-pe/weak-ext-expected3-add1-weak.c | 9 ++ ld/testsuite/ld-pe/weak-ext-expected3.c | 1 + ld/testsuite/ld-pe/weak-ext-expected5.c | 1 + ld/testsuite/ld-pe/weak-ext-main-weak-def.c | 26 ++++++ ld/testsuite/ld-pe/weak-ext-main-weak.c | 22 +++++ ld/testsuite/ld-pe/weak-ext-main.c | 21 +++++ 11 files changed, 195 insertions(+) create mode 100644 ld/testsuite/ld-pe/weak-ext-add1-weak-chained.c create mode 100644 ld/testsuite/ld-pe/weak-ext-add2.c create mode 100644 ld/testsuite/ld-pe/weak-ext-dummy.c create mode 100644 ld/testsuite/ld-pe/weak-ext-expected1.c create mode 100644 ld/testsuite/ld-pe/weak-ext-expected3-add1-weak.c create mode 100644 ld/testsuite/ld-pe/weak-ext-expected3.c create mode 100644 ld/testsuite/ld-pe/weak-ext-expected5.c create mode 100644 ld/testsuite/ld-pe/weak-ext-main-weak-def.c create mode 100644 ld/testsuite/ld-pe/weak-ext-main-weak.c create mode 100644 ld/testsuite/ld-pe/weak-ext-main.c diff --git a/ld/testsuite/ld-pe/pe-compile.exp b/ld/testsuite/ld-pe/pe-compile.exp index 041a1cb9344..189b95ea315 100644 --- a/ld/testsuite/ld-pe/pe-compile.exp +++ b/ld/testsuite/ld-pe/pe-compile.exp @@ -138,3 +138,91 @@ set align_tests { } run_ld_link_tests $align_tests + +# Test PE COFF weak external symbol resolution. +# These tests cover the full matrix of weak/strong interactions to verify +# that the linker correctly resolves weak externals in PE COFF objects. +# Based on testcases by Martin Storsjo. + +proc weak_ext_test { testname sources } { + global CC_FOR_TARGET + global srcdir + global subdir + + set objfiles {} + foreach src $sources { + set fileroot [file rootname [file tail $src]] + set obj "tmpdir/$fileroot.o" + if ![ld_compile $CC_FOR_TARGET $srcdir/$subdir/$src $obj] { + fail "$testname (compile $src)" + return + } + lappend objfiles $obj + } + + set output "tmpdir/weak-ext-test.exe" + if ![ld_link $CC_FOR_TARGET $output $objfiles] { + fail "$testname (link)" + return + } + + if ![isnative] { + pass "$testname (link only)" + return + } + + catch "exec $output" prog_output + if { $prog_output eq "" } { + pass $testname + } else { + verbose $prog_output + fail "$testname ($prog_output)" + } +} + +# Strong undefined reference + strong definition: basic sanity check. +weak_ext_test "weak external: normal (strong undef + strong def)" \ + {weak-ext-main.c weak-ext-add2.c weak-ext-expected3.c weak-ext-dummy.c} + +# Weak declaration with no definition available: func remains NULL, +# never called. +weak_ext_test "weak external: weak undef (no def)" \ + {weak-ext-main-weak.c weak-ext-expected1.c weak-ext-dummy.c} + +# Weak declaration resolved by a strong definition in another object. +weak_ext_test "weak external: weak decl + strong def" \ + {weak-ext-main-weak.c weak-ext-add2.c weak-ext-expected3.c weak-ext-dummy.c} + +# Two weak externals for the same symbol meet: the one whose fallback +# alias points to an actual function body should win over the one whose +# fallback is NULL. +weak_ext_test "weak external: weak decl + weak def" \ + {weak-ext-main-weak.c weak-ext-add1-weak-chained.c weak-ext-expected3.c} + +# Strong undefined reference is seen before the weak definition: the +# linker must still store the weak external's aux record so the fallback +# alias can be resolved later. +weak_ext_test "weak external: strong undef + weak def" \ + {weak-ext-main.c weak-ext-add1-weak-chained.c weak-ext-expected3.c} + +# Strong definition overrides a weak definition: both call sites (in +# main and in dummy) should resolve to the strong def. +weak_ext_test "weak external: strong override of weak" \ + {weak-ext-main.c weak-ext-add1-weak-chained.c weak-ext-add2.c \ + weak-ext-expected5.c} + +# Two identical weak definitions for the same symbol: no conflict, +# the linker picks one (first wins) and discards the duplicate. +weak_ext_test "weak external: duplicate weak defs" \ + {weak-ext-main.c weak-ext-add1-weak-chained.c \ + weak-ext-expected3-add1-weak.c} + +# Weak definition in main overridden by a strong definition elsewhere. +weak_ext_test "weak external: weak def overridden by strong" \ + {weak-ext-main-weak-def.c weak-ext-add2.c weak-ext-expected3.c \ + weak-ext-dummy.c} + +# Weak definition in main used directly when no strong definition exists. +weak_ext_test "weak external: weak def used (no strong)" \ + {weak-ext-main-weak-def.c weak-ext-expected5.c weak-ext-dummy.c} + diff --git a/ld/testsuite/ld-pe/weak-ext-add1-weak-chained.c b/ld/testsuite/ld-pe/weak-ext-add1-weak-chained.c new file mode 100644 index 00000000000..5b68b8435e2 --- /dev/null +++ b/ld/testsuite/ld-pe/weak-ext-add1-weak-chained.c @@ -0,0 +1,13 @@ +/* Weak definition of func (adds 1), plus dummy that calls func. */ +extern int value; + +__attribute__((weak)) void func (void) +{ + value += 1; +} + +void +dummy (void) +{ + func (); +} diff --git a/ld/testsuite/ld-pe/weak-ext-add2.c b/ld/testsuite/ld-pe/weak-ext-add2.c new file mode 100644 index 00000000000..691554d17e3 --- /dev/null +++ b/ld/testsuite/ld-pe/weak-ext-add2.c @@ -0,0 +1,8 @@ +/* Strong definition of func (adds 2). */ +extern int value; + +void +func (void) +{ + value += 2; +} diff --git a/ld/testsuite/ld-pe/weak-ext-dummy.c b/ld/testsuite/ld-pe/weak-ext-dummy.c new file mode 100644 index 00000000000..1c97c3cf97f --- /dev/null +++ b/ld/testsuite/ld-pe/weak-ext-dummy.c @@ -0,0 +1,5 @@ +/* Empty dummy function. */ +void +dummy (void) +{ +} diff --git a/ld/testsuite/ld-pe/weak-ext-expected1.c b/ld/testsuite/ld-pe/weak-ext-expected1.c new file mode 100644 index 00000000000..5b4138b0bb1 --- /dev/null +++ b/ld/testsuite/ld-pe/weak-ext-expected1.c @@ -0,0 +1 @@ +int expected = 1; diff --git a/ld/testsuite/ld-pe/weak-ext-expected3-add1-weak.c b/ld/testsuite/ld-pe/weak-ext-expected3-add1-weak.c new file mode 100644 index 00000000000..585830bd304 --- /dev/null +++ b/ld/testsuite/ld-pe/weak-ext-expected3-add1-weak.c @@ -0,0 +1,9 @@ +/* Second weak definition of func (adds 1) plus expected value. */ +extern int value; + +__attribute__((weak)) void func (void) +{ + value += 1; +} + +int expected = 3; diff --git a/ld/testsuite/ld-pe/weak-ext-expected3.c b/ld/testsuite/ld-pe/weak-ext-expected3.c new file mode 100644 index 00000000000..3d67d70bcb0 --- /dev/null +++ b/ld/testsuite/ld-pe/weak-ext-expected3.c @@ -0,0 +1 @@ +int expected = 3; diff --git a/ld/testsuite/ld-pe/weak-ext-expected5.c b/ld/testsuite/ld-pe/weak-ext-expected5.c new file mode 100644 index 00000000000..fef6b322c03 --- /dev/null +++ b/ld/testsuite/ld-pe/weak-ext-expected5.c @@ -0,0 +1 @@ +int expected = 5; diff --git a/ld/testsuite/ld-pe/weak-ext-main-weak-def.c b/ld/testsuite/ld-pe/weak-ext-main-weak-def.c new file mode 100644 index 00000000000..05f81d189b4 --- /dev/null +++ b/ld/testsuite/ld-pe/weak-ext-main-weak-def.c @@ -0,0 +1,26 @@ +/* Weak definition of func in main (adds 4). */ +#include + +int value = 1; + +__attribute__((weak)) void func (void) +{ + value += 4; +} + +void dummy (void); +extern int expected; + +int +main (void) +{ + if (func) + func (); + dummy (); + if (value != expected) + { + printf ("expected %d, got %d\n", expected, value); + return 1; + } + return 0; +} diff --git a/ld/testsuite/ld-pe/weak-ext-main-weak.c b/ld/testsuite/ld-pe/weak-ext-main-weak.c new file mode 100644 index 00000000000..bc1501f62c0 --- /dev/null +++ b/ld/testsuite/ld-pe/weak-ext-main-weak.c @@ -0,0 +1,22 @@ +/* Weak declaration of func (no definition). */ +#include + +__attribute__((weak)) void func (void); +void dummy (void); + +int value = 1; +extern int expected; + +int +main (void) +{ + if (func) + func (); + dummy (); + if (value != expected) + { + printf ("expected %d, got %d\n", expected, value); + return 1; + } + return 0; +} diff --git a/ld/testsuite/ld-pe/weak-ext-main.c b/ld/testsuite/ld-pe/weak-ext-main.c new file mode 100644 index 00000000000..916bb246e5f --- /dev/null +++ b/ld/testsuite/ld-pe/weak-ext-main.c @@ -0,0 +1,21 @@ +/* Strong undefined reference to func. */ +#include + +void func (void); +void dummy (void); + +int value = 1; +extern int expected; + +int +main (void) +{ + func (); + dummy (); + if (value != expected) + { + printf ("expected %d, got %d\n", expected, value); + return 1; + } + return 0; +}