[llvm-branch-commits] [llvm] release/23.x: [llvm-objcopy][COFF] Keep COMDAT section definition symbols when stripping (#228783) (PR #229026)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Mon Oct 5 02:34:28 PDT 2026


https://github.com/llvmbot created https://github.com/llvm/llvm-project/pull/229026

Backport cf40a83cb398897d21bb54c5a7a2eecb8d0633b3

Requested by: @mstorsjo

>From 58f48c3434e7485b7d7d77e291d6e45020f6e6de Mon Sep 17 00:00:00 2001
From: BtbN <btbn at btbn.de>
Date: Mon, 5 Oct 2026 00:02:43 +0200
Subject: [PATCH] [llvm-objcopy][COFF] Keep COMDAT section definition symbols
 when stripping (#228783)

--strip-unneeded and --discard-all removed the section definition symbol of
a COMDAT section if no relocation referenced it directly, which is the
common case since relocations target the COMDAT leader. The aux record of
that symbol carries the COMDAT selection; without it lld discards the
section as a leaderless COMDAT and silently drops relocations against it.

This broke e.g. .refptr.* sections in MinGW objects, producing executables
that load garbage instead of the referenced pointer.

Assisted-by: Claude Opus 5.5
(cherry picked from commit cf40a83cb398897d21bb54c5a7a2eecb8d0633b3)
---
 llvm/lib/ObjCopy/COFF/COFFObjcopy.cpp         | 16 ++++
 .../COFF/strip-unneeded-comdat.test           | 94 +++++++++++++++++++
 2 files changed, 110 insertions(+)
 create mode 100644 llvm/test/tools/llvm-objcopy/COFF/strip-unneeded-comdat.test

diff --git a/llvm/lib/ObjCopy/COFF/COFFObjcopy.cpp b/llvm/lib/ObjCopy/COFF/COFFObjcopy.cpp
index 48c488826c47d..96de61737d78f 100644
--- a/llvm/lib/ObjCopy/COFF/COFFObjcopy.cpp
+++ b/llvm/lib/ObjCopy/COFF/COFFObjcopy.cpp
@@ -32,6 +32,19 @@ static bool isDebugSection(const Section &Sec) {
   return Sec.Name.starts_with(".debug");
 }
 
+// The COMDAT selection is stored in the section definition symbol's aux
+// record, so it must be kept even if no relocation references it.
+static bool isComdatSectionDefinition(const Object &Obj, const Symbol &Sym) {
+  if (Sym.Sym.StorageClass == IMAGE_SYM_CLASS_STATIC && Sym.Sym.Value == 0 &&
+      Sym.Sym.NumberOfAuxSymbols != 0 && Sym.TargetSectionId > 0) {
+    // Looks like a section definition symbol, check if it is a COMDAT.
+    const Section *Sec = Obj.findSection(Sym.TargetSectionId);
+    return Sec && Sec->Name == Sym.Name &&
+           (Sec->Header.Characteristics & IMAGE_SCN_LNK_COMDAT);
+  }
+  return false;
+}
+
 static uint64_t getNextRVA(const Object &Obj) {
   if (Obj.getSections().empty())
     return 0;
@@ -233,6 +246,9 @@ static Error handleArgs(const CommonConfig &Config,
       return true;
     }
 
+    if (isComdatSectionDefinition(Obj, Sym))
+      return false;
+
     if (!Sym.Referenced) {
       // With --strip-unneeded, GNU objcopy removes all unreferenced local
       // symbols, and any unreferenced undefined external.
diff --git a/llvm/test/tools/llvm-objcopy/COFF/strip-unneeded-comdat.test b/llvm/test/tools/llvm-objcopy/COFF/strip-unneeded-comdat.test
new file mode 100644
index 0000000000000..8af57d031ee87
--- /dev/null
+++ b/llvm/test/tools/llvm-objcopy/COFF/strip-unneeded-comdat.test
@@ -0,0 +1,94 @@
+## Check that --strip-unneeded and --discard-all keep the section definition
+## symbol of a COMDAT section, even if no relocation references it. It carries
+## the COMDAT selection, without it the linker discards the section.
+
+# RUN: yaml2obj %s -o %t.in.o
+# RUN: llvm-objcopy --strip-unneeded %t.in.o %t.strip-unneeded.o
+# RUN: llvm-readobj --symbols %t.strip-unneeded.o | FileCheck %s
+# RUN: llvm-objcopy --discard-all %t.in.o %t.discard-all.o
+# RUN: llvm-readobj --symbols %t.discard-all.o | FileCheck %s
+
+# CHECK:      Symbols [
+# CHECK-NEXT:   Symbol {
+# CHECK-NEXT:     Name: .rdata$.refptr.foo
+# CHECK-NEXT:     Value: 0
+# CHECK-NEXT:     Section: .rdata$.refptr.foo (2)
+# CHECK-NEXT:     BaseType: Null (0x0)
+# CHECK-NEXT:     ComplexType: Null (0x0)
+# CHECK-NEXT:     StorageClass: Static (0x3)
+# CHECK-NEXT:     AuxSymbolCount: 1
+# CHECK-NEXT:     AuxSectionDef {
+# CHECK-NEXT:       Length: 8
+# CHECK-NEXT:       RelocationCount: 1
+# CHECK-NEXT:       LineNumberCount: 0
+# CHECK-NEXT:       Checksum: 0x0
+# CHECK-NEXT:       Number: 2
+# CHECK-NEXT:       Selection: Any (0x2)
+# CHECK-NEXT:     }
+# CHECK-NEXT:   }
+# CHECK-NEXT:   Symbol {
+# CHECK-NEXT:     Name: .refptr.foo
+# CHECK:        Symbol {
+# CHECK-NEXT:     Name: foo
+# CHECK-NOT:      Name: .text
+
+--- !COFF
+header:
+  Machine:         IMAGE_FILE_MACHINE_AMD64
+  Characteristics: [  ]
+sections:
+  - Name:            .text
+    Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+    Alignment:       16
+    SectionData:     488B0500000000C3
+    Relocations:
+      - VirtualAddress:  3
+        SymbolName:      .refptr.foo
+        Type:            IMAGE_REL_AMD64_REL32
+  - Name:            '.rdata$.refptr.foo'
+    Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_LNK_COMDAT, IMAGE_SCN_MEM_READ ]
+    Alignment:       8
+    SectionData:     '0000000000000000'
+    Relocations:
+      - VirtualAddress:  0
+        SymbolName:      foo
+        Type:            IMAGE_REL_AMD64_ADDR64
+symbols:
+  - Name:            .text
+    Value:           0
+    SectionNumber:   1
+    SimpleType:      IMAGE_SYM_TYPE_NULL
+    ComplexType:     IMAGE_SYM_DTYPE_NULL
+    StorageClass:    IMAGE_SYM_CLASS_STATIC
+    SectionDefinition:
+      Length:          8
+      NumberOfRelocations: 1
+      NumberOfLinenumbers: 0
+      CheckSum:        0
+      Number:          1
+  - Name:            '.rdata$.refptr.foo'
+    Value:           0
+    SectionNumber:   2
+    SimpleType:      IMAGE_SYM_TYPE_NULL
+    ComplexType:     IMAGE_SYM_DTYPE_NULL
+    StorageClass:    IMAGE_SYM_CLASS_STATIC
+    SectionDefinition:
+      Length:          8
+      NumberOfRelocations: 1
+      NumberOfLinenumbers: 0
+      CheckSum:        0
+      Number:          2
+      Selection:       IMAGE_COMDAT_SELECT_ANY
+  - Name:            .refptr.foo
+    Value:           0
+    SectionNumber:   2
+    SimpleType:      IMAGE_SYM_TYPE_NULL
+    ComplexType:     IMAGE_SYM_DTYPE_NULL
+    StorageClass:    IMAGE_SYM_CLASS_EXTERNAL
+  - Name:            foo
+    Value:           0
+    SectionNumber:   0
+    SimpleType:      IMAGE_SYM_TYPE_NULL
+    ComplexType:     IMAGE_SYM_DTYPE_NULL
+    StorageClass:    IMAGE_SYM_CLASS_EXTERNAL
+...



More information about the llvm-branch-commits mailing list