Skip to content

Commit 6b0eb5a

Browse files
committed
[ELF] Improve --gc-sections compatibility with GNU ld regarding section groups
Based on D70020 by serge-sans-paille. The ELF spec says: > Furthermore, there may be internal references among these sections that would not make sense if one of the sections were removed or replaced by a duplicate from another object. Therefore, such groups must be included or omitted from the linked object as a unit. A section cannot be a member of more than one group. GNU ld has 2 behaviors that we don't have: - Group members (nextInSectionGroup != nullptr) are subject to garbage collection. This includes non-SHF_ALLOC SHT_NOTE sections. In particular, discarding non-SHF_ALLOC SHT_NOTE sections is an expected behavior by the Annobin project. See https://developers.redhat.com/blog/2018/02/20/annobin-storing-information-binaries/ for more information. - Groups members are retained or discarded as a unit. Members may have internal references that are not expressed as SHF_LINK_ORDER, relocations, etc. It seems that we should be more conservative here: if a section is marked live, mark all the other member within the group. Both behaviors are reasonable. This patch implements them. A new field InputSectionBase::nextInSectionGroup tracks the next member within a group. on ELF64, this increases sizeof(InputSectionBase) froms 144 to 152. InputSectionBase::dependentSections tracks section dependencies, which is used by both --gc-sections and /DISCARD/. We can't overload it for the "next member" semantic, because we should allow /DISCARD/ to discard sections independent of --gc-sections (GNU ld behavior). This behavior may be reasonably used by `/DISCARD/ : { *(.ARM.exidx*) }` or `/DISCARD/ : { *(.note*) }` (new test `linkerscript/discard-group.s`). Reviewed By: ruiu Differential Revision: https://reviews.llvm.org/D70146
1 parent 3a76b8a commit 6b0eb5a

File tree

5 files changed

+129
-2
lines changed

5 files changed

+129
-2
lines changed

lld/ELF/InputFiles.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,8 @@ void ObjFile<ELFT>::initializeSections(bool ignoreComdats) {
507507
this->sectionStringTable =
508508
CHECK(obj.getSectionStringTable(objSections), this);
509509

510+
std::vector<ArrayRef<Elf_Word>> selectedGroups;
511+
510512
for (size_t i = 0, e = objSections.size(); i < e; ++i) {
511513
if (this->sections[i] == &InputSection::discarded)
512514
continue;
@@ -564,6 +566,7 @@ void ObjFile<ELFT>::initializeSections(bool ignoreComdats) {
564566
if (isNew) {
565567
if (config->relocatable)
566568
this->sections[i] = createInputSection(sec);
569+
selectedGroups.push_back(entries);
567570
continue;
568571
}
569572

@@ -588,6 +591,7 @@ void ObjFile<ELFT>::initializeSections(bool ignoreComdats) {
588591
}
589592
}
590593

594+
// This block handles SHF_LINK_ORDER.
591595
for (size_t i = 0, e = objSections.size(); i < e; ++i) {
592596
if (this->sections[i] == &InputSection::discarded)
593597
continue;
@@ -610,6 +614,25 @@ void ObjFile<ELFT>::initializeSections(bool ignoreComdats) {
610614
" with SHF_LINK_ORDER should not refer a non-regular section: " +
611615
toString(linkSec));
612616
}
617+
618+
// For each secion group, connect its members in a circular doubly-linked list
619+
// via nextInSectionGroup. See the comment in markLive().
620+
for (ArrayRef<Elf_Word> entries : selectedGroups) {
621+
InputSectionBase *head;
622+
InputSectionBase *prev = nullptr;
623+
for (uint32_t secIndex : entries.slice(1)) {
624+
InputSectionBase *s = this->sections[secIndex];
625+
if (!s || s == &InputSection::discarded)
626+
continue;
627+
if (prev)
628+
prev->nextInSectionGroup = s;
629+
else
630+
head = s;
631+
prev = s;
632+
}
633+
if (prev)
634+
prev->nextInSectionGroup = head;
635+
}
613636
}
614637

615638
// For ARM only, to set the EF_ARM_ABI_FLOAT_SOFT or EF_ARM_ABI_FLOAT_HARD

lld/ELF/InputSection.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ class InputSectionBase : public SectionBase {
142142
// cases this points one level up.
143143
SectionBase *parent = nullptr;
144144

145+
// The next member in the section group if this section is in a group. This is
146+
// used by --gc-sections.
147+
InputSectionBase *nextInSectionGroup = nullptr;
148+
145149
template <class ELFT> ArrayRef<typename ELFT::Rel> rels() const {
146150
assert(!areRelocsRela);
147151
return llvm::makeArrayRef(

lld/ELF/MarkLive.cpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,11 @@ static bool isReserved(InputSectionBase *sec) {
165165
switch (sec->type) {
166166
case SHT_FINI_ARRAY:
167167
case SHT_INIT_ARRAY:
168-
case SHT_NOTE:
169168
case SHT_PREINIT_ARRAY:
170169
return true;
170+
case SHT_NOTE:
171+
// SHT_NOTE sections in a group are subject to garbage collection.
172+
return !sec->nextInSectionGroup;
171173
default:
172174
StringRef s = sec->name;
173175
return s.startswith(".ctors") || s.startswith(".dtors") ||
@@ -283,6 +285,10 @@ template <class ELFT> void MarkLive<ELFT>::mark() {
283285

284286
for (InputSectionBase *isec : sec.dependentSections)
285287
enqueue(isec, 0);
288+
289+
// Mark the next group member.
290+
if (sec.nextInSectionGroup)
291+
enqueue(sec.nextInSectionGroup, 0);
286292
}
287293
}
288294

@@ -353,12 +359,19 @@ template <class ELFT> void markLive() {
353359
// or -emit-reloc were given. And they are subject of garbage
354360
// collection because, if we remove a text section, we also
355361
// remove its relocation section.
362+
//
363+
// Note on nextInSectionGroup: The ELF spec says that group sections are
364+
// included or omitted as a unit. We take the interpretation that:
365+
//
366+
// - Group members (nextInSectionGroup != nullptr) are subject to garbage
367+
// collection.
368+
// - Groups members are retained or discarded as a unit.
356369
for (InputSectionBase *sec : inputSections) {
357370
bool isAlloc = (sec->flags & SHF_ALLOC);
358371
bool isLinkOrder = (sec->flags & SHF_LINK_ORDER);
359372
bool isRel = (sec->type == SHT_REL || sec->type == SHT_RELA);
360373

361-
if (!isAlloc && !isLinkOrder && !isRel)
374+
if (!isAlloc && !isLinkOrder && !isRel && !sec->nextInSectionGroup)
362375
sec->markLive();
363376
}
364377

lld/test/ELF/gc-sections-group.s

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# REQUIRES: x86
2+
## Check that group members are retained or discarded as a unit, and
3+
## non-SHF_ALLOC sections in a group are subject to garbage collection.
4+
## This is compatible with GNU ld.
5+
6+
# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o
7+
# RUN: ld.lld --gc-sections %t.o -o %t.dead
8+
# RUN: llvm-readobj -S %t.dead | FileCheck %s --check-prefix=CHECK-DEAD
9+
10+
## .mynote.bar is retained because it is not in a group.
11+
# CHECK-DEAD-NOT: Name: .myanote.foo
12+
# CHECK-DEAD-NOT: Name: .mytext.foo
13+
# CHECK-DEAD-NOT: Name: .mybss.foo
14+
# CHECK-DEAD-NOT: Name: .mynote.foo
15+
# CHECK-DEAD: Name: .mynote.bar
16+
17+
# RUN: ld.lld --gc-sections %t.o -o %t -e anote_foo
18+
# RUN: llvm-readobj -S %t | FileCheck %s --check-prefix=CHECK-LIVE
19+
# RUN: ld.lld --gc-sections %t.o -o %t -e foo
20+
# RUN: llvm-readobj -S %t | FileCheck %s --check-prefix=CHECK-LIVE
21+
# RUN: ld.lld --gc-sections %t.o -o %t -e bss_foo
22+
# RUN: llvm-readobj -S %t | FileCheck %s --check-prefix=CHECK-LIVE
23+
24+
## note_foo as the entry point does not make much sense because it is defined
25+
## in a non-SHF_ALLOC section. This is just to demonstrate the behavior.
26+
# RUN: ld.lld --gc-sections %t.o -o %t -e note_foo
27+
# RUN: llvm-readobj -S %t | FileCheck %s --check-prefix=CHECK-LIVE
28+
29+
# CHECK-LIVE: Name: .myanote.foo
30+
# CHECK-LIVE: Name: .mytext.foo
31+
# CHECK-LIVE: Name: .mybss.foo
32+
# CHECK-LIVE: Name: .mynote.foo
33+
# CHECK-LIVE: Name: .mynote.bar
34+
35+
.globl anote_foo, foo, bss_foo, note_foo
36+
37+
.section .myanote.foo,"aG",@note,foo,comdat
38+
anote_foo:
39+
.byte 0
40+
41+
.section .mytext.foo,"axG",@progbits,foo,comdat
42+
foo:
43+
.byte 0
44+
45+
.section .mybss.foo,"awG",@nobits,foo,comdat
46+
bss_foo:
47+
.byte 0
48+
49+
.section .mynote.foo,"G",@note,foo,comdat
50+
note_foo:
51+
.byte 0
52+
53+
.section .mynote.bar,"",@note
54+
.byte 0
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# REQUIRES: arm
2+
## For --gc-sections, group members are retained or discarded as a unit.
3+
## However, discarding a section via /DISCARD/ should not discard other members
4+
## within the group. This is compatible with GNU ld.
5+
6+
# RUN: llvm-mc -filetype=obj -triple=armv7a-none-linux-gnueabi %s -o %t.o
7+
8+
## We can discard .ARM.exidx* in a group.
9+
# RUN: echo 'SECTIONS { /DISCARD/ : { *(.ARM.exidx*) }}' > %t.noarm.script
10+
# RUN: ld.lld %t.o --gc-sections -T %t.noarm.script -o %t.noarm
11+
# RUN: llvm-readobj -S %t.noarm | FileCheck %s --check-prefix=NOARM --implicit-check-not='Name: .ARM.exidx'
12+
13+
# NOARM: Name: .text
14+
# NOARM: Name: .note._start
15+
16+
## Another example, we can discard SHT_NOTE in a group.
17+
# RUN: echo 'SECTIONS { /DISCARD/ : { *(.note*) }}' > %t.nonote.script
18+
# RUN: ld.lld %t.o --gc-sections -T %t.nonote.script -o %t.nonote
19+
# RUN: llvm-readobj -S %t.nonote | FileCheck %s --check-prefix=NONOTE --implicit-check-not='Name: .note'
20+
21+
# NONOTE: Name: .ARM.exidx
22+
# NONOTE: Name: .text
23+
24+
.section .text._start,"axG",%progbits,_start,comdat
25+
.globl _start
26+
_start:
27+
.fnstart
28+
.cantunwind
29+
bx lr
30+
.fnend
31+
32+
.section .note._start,"G",%note,_start,comdat
33+
.byte 0

0 commit comments

Comments
 (0)