[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