Index: ELF/InputFiles.cpp =================================================================== --- ELF/InputFiles.cpp +++ ELF/InputFiles.cpp @@ -326,6 +326,27 @@ IS->DependentSection = Sections[I]; } } + + // Handle SHT_GROUP by giving all members of the selected group a GC link back + // to the group section, and discarding the rest. This is done after the main + // loop because it needs group member sections to be initialized. + I = -1; + for (const Elf_Shdr &Sec : ObjSections) { + ++I; + if (Sec.sh_type != SHT_GROUP) + continue; + + ArrayRef GroupSections = getShtGroupEntries(Sec); + for (uint32_t SecIndex : GroupSections) { + if (SecIndex >= Size) + fatal(toString(this) + + ": invalid section index in group: " + Twine(SecIndex)); + if (!Sections[SecIndex] || + Sections[SecIndex] == &InputSection::Discarded) + continue; + Sections[SecIndex]->GroupSections = GroupSections; + } + } } template Index: ELF/InputSection.h =================================================================== --- ELF/InputSection.h +++ ELF/InputSection.h @@ -92,6 +92,13 @@ uint32_t Link; uint32_t Info; + // If this section is part of a section group, this list contains indices of + // all sections in that group. Section groups are treated as a unit in garbage + // collection. Even through it is not clearly documented in the standard, this + // matches the BFD linker behavior and is also important for AddressSanitizer + // (see https://github.com/google/sanitizers/issues/260). + ArrayRef GroupSections; + InputSectionBase() : InputSectionData(Regular, "", ArrayRef(), false), Repl(this) { NumRelocations = 0; Index: ELF/MarkLive.cpp =================================================================== --- ELF/MarkLive.cpp +++ ELF/MarkLive.cpp @@ -81,14 +81,25 @@ static void forEachSuccessor(InputSection &Sec, std::function)> Fn) { if (Sec.AreRelocsRela) { + // Visit relocations. for (const typename ELFT::Rela &Rel : Sec.relas()) Fn(resolveReloc(Sec, Rel)); } else { for (const typename ELFT::Rel &Rel : Sec.rels()) Fn(resolveReloc(Sec, Rel)); } + // Visit a reverse link of SHF_LINK_ORDER if exists. if (Sec.DependentSection) Fn({Sec.DependentSection, 0}); + // Visit SHT_GROUP members if this section is in a group. + for (auto SecIdx : Sec.GroupSections) { + InputSectionBase *SameGroupSection = + Sec.getFile()->getSections()[SecIdx]; + if (SameGroupSection && + SameGroupSection != &InputSection::Discarded && + SameGroupSection != &Sec) + Fn({SameGroupSection, 0}); + } } // The .eh_frame section is an unfortunate special case. @@ -160,6 +171,22 @@ // 1) Sections used by the loader (.init, .fini, .ctors, .dtors or .jcr) // 2) Non-allocatable sections which typically contain debugging information template static bool isReserved(InputSectionBase *Sec) { + // Sections that are part of a group follow the usual GC rules. + // + // This allows discarding special sections that are associated with regular + // sections. As an example, an .init_array pointer for a global initializer + // can be discarded if the global is unused (and that, in turn, would allow to + // discard the global itself). + // + // Another example is AddressSanitizer, which attaches special descriptors to + // all globals and collects them in a specially-named section. Even though the + // name of that section isValidCIdentifier(), the descriptors should not + // affect the liveness of corresponding globals in any way, which mean that + // should not be added to GC roots based on the section name. See + // https://github.com/google/sanitizers/issues/260 for more context. + if (Sec->GroupSections.size() > 0) + return false; + switch (Sec->Type) { case SHT_FINI_ARRAY: case SHT_INIT_ARRAY: Index: test/ELF/comdat-gc.s =================================================================== --- /dev/null +++ test/ELF/comdat-gc.s @@ -0,0 +1,38 @@ +// Test that GC respects section groups. +// RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t.o +// RUN: ld.lld -shared -gc-sections %t.o -o %t +// RUN: llvm-objdump -t %t | FileCheck --check-prefix=SYM %s +// RUN: llvm-objdump -section-headers %t | FileCheck --check-prefix=SEC %s + + .text + .section .bss.x,"aGw",@nobits,x,comdat + .globl x +x: + .quad 0 + .section .bss.y,"aGw",@nobits,x,comdat +y: + .quad 0 +// "x" is a GC root (external global in a +// shared library), "y" is in the same section group as "x" and therefore must +// not be discarded. +// SYM-DAG: .bss{{.*}} y{{$}} + + .section zzz_no_comdat,"aw",@nobits +z1: + .quad 0 +// A section with a name that is a valid C identifier, not part of any section +// group => GC root. +// SYM-DAG: zzz_no_comdat{{.*}} z1{{$}} + + .section zzz_comdat,"aGw",@nobits,z2,comdat +z2: + .quad 0 +// No special treatment if the section is in a section group. +// SEC-NOT: zzz_comdat + + .section .ctors,"aGw",@progbits,z3,comdat +z3: + .quad 0 +// No special treatment if the section is in a section group, even for magic +// sections like .ctors. +// SEC-NOT: .ctors